Repository navigation
Releases
v2.4.1
Mercur v2.4.1
A hardening and performance release. Product and store offer lists now run on the Medusa index engine with the Caching Module switched on, a core security audit closes a long list of exposure and ownership gaps, operators get a Products → Inbox for everything stores submit, and Medusa moves to 2.22.3. Plus a batch of fixes around split-order checkout and the dashboard SDK.
⚠️ Before you upgrade
- Run
medusa db:migrate. It hashes member-invite tokens in place (member_invite.token_hash, #1739) and creates the writableoffer_seller/offer_productlink tables, then backfills them for existing offers (#1747). - Run one full index sync (
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 (#1740, #1747). - Caching is on by default.
withMercurnow enables thecachingflag and registers the Medusa Caching Module (opt out withfeatureFlags.caching: false). It uses Redis whenprojectConfig.redisUrlis set and an in-memory store otherwise, so multi-instance deployments should setredisUrl(#1740).@medusajs/indexand@medusajs/caching-redisare direct dependencies of the API app and the basic template now. - Removed or tightened API surface (#1739):
POST /admin/sellers/:idno longer acceptsstatus/status_reason(use the lifecycle endpoints) andis_premiumneedssellers.premium:edit;GET /vendor/sellers/meno longer returns payment or professional details (read them fromGET /vendor/sellers/:idbehindstore:view); the admin payout list no longer returnsdataand storedsource_paymentkeeps onlyidandlatest_charge; admin seller and payout-account defaults droponboarding.data;GET /vendor/feature-flagsreturns an allowlist; the invite token is returned only from create and resend;address_idis gone from vendor stock-location validators; suspended and terminated sellers lose Vendor API access exceptGET /vendor/sellers/me; vendor uploads accept images only. - Medusa 2.22.3 (#1752):
BigNumber/MathBNnow throw on invalid input instead of returningNaN, and a malformedbodyParser.sizeLimitthrows when the route is hit.
✨ Highlights
🔎 Product and offer lists on the Medusa 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, and all product lists are cached (store 300s, admin and vendor 60s) with explicit invalidation tags. (#1740, #1747, #1750)
🛡️ Core security hardening
A full audit of @mercurjs/core. 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 gained 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. Closes GHSA-r7gm-rgf8-5q8m and the remainder of GHSA-mhch-gq7f-v5p7. (#1739)
📥 Products → Inbox for operators
One admin table of everything stores are waiting on: newly proposed products and pending edit or deletion requests, with search, Type / Store / date filters, sorting and pagination, 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. (#1751)
4.2.1.mp4
💬 Feedback from the dashboards
Both panels show the Mercur version in the topbar next to a Feedback button that sends a message to the Mercur team. (#1749)
Platform
- Bump Medusa to 2.22.3 (#1752). The order cross-module count fix was upstreamed, so that patch is gone; the four core-flows patches apply unchanged.
🐛 Fixes
- Keep 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 (#1745) - Refund a captured payment when split-order cart completion fails after a
capturedwebhook (#1741) - Dashboard SDK virtual modules import only the files they read, fixing the blank panel in Vite dev when a route imports the
pagesentry (#1744) - Keep the index engine Product sync off the review and pricing graphs, fixing the "Cannot read properties of undefined (reading 'id')" sync error on boot (#1750)
create-mercur-appchecks PostgreSQL and creates the database before scaffolding, and tells you how to fix a server, credential or database-name problem; each telemetry milestone is sent exactly once (#1748)- Seller
created/updatedevents are emitted again, so cached seller reads invalidate and subscribers run (#1740) - Name the product-delete
whenblock inapply-product-change-actions(#1743) - Bump
sanitize-htmlto 2.18.1, andzod,i18next-http-backendandvitestto patched versions (#1742, #1737, #1716)
📚 Docs
- Fix broken links and references to the deleted
newbranch (#1636) - Fix typos in the README contribution section (#1726)
🙌 New contributors
- @RashidMahmood25 made their first contribution in #1726 — thank you!
Full Changelog: v2.4.0...v2.4.1
v2.4.0
Mercur v2.4.0
Mercur now connects to more of the outside world. Payouts move to a thin core
module plus a new Stripe connector plugin, and a new Magento 2 connector lets
an external storefront sell marketplace offers. Operators can assign sellers
to regions, the panels get a layout composer and much deeper extension points,
and checkout and vendor lists get a serious performance pass. We also bumped
Medusa to 2.22.0, which requires Node 22.22+, and fixed a batch of cart, offer
and panel bugs. This release has breaking changes, so please read the section
below first.
⚠️ Before you upgrade
- Node 22.22+ and Medusa 2.22.0 (#1685). Medusa 2.22.0 drops Node 20. To pick
up the October 2026 Node.js security releases, we recommend Node 24.22.1+, or
22.23.4+ if you stay on Node 22. - Run migrations. They restructure the payout tables, add the
region_sellerlink,order_group.metadata, a new telemetry table and list
indexes, and makeseller.default_leadtime_to_shipnullable. @mercurjs/payout-stripe-connectis removed (#1693). Install
@mercurjs/stripe-connector-pluginand point the payout module at
@mercurjs/stripe-connector-plugin/providers/payout-stripe-connect. If you
also adopt the plugin's payment provider, addpp_stripe-connector_<id>to
your regions.- The payout module API changed (#1693).
createPayoutWorkflowis replaced bycreateOrderPayoutsWorkflow.- On
IPayoutProvider,createPayoutAccountbecomescreateAccount, and
retrieveAccountandreversePayoutare now required. PayoutStatus.CANCELEDbecomesABORTED; existing rows are migrated.
- Existing sellers have no regions until you assign them (#1665, #1696).
GET /vendor/regionsnow returns assigned regions only,
GET /vendor/regions/:idreturns 404 for the others, and a vendor offer or
shipping price on an unassigned region returns 400. Set a default region
under Settings → Store for new sellers, then link existing ones from the
seller page or withPOST /admin/sellers/:id/regionsand
{ "add": ["reg_…"] }. - New sellers no longer default to a lead time of 2 (#1687, #1730). They
start atnulland use the marketplace default from Settings → Marketplace.
Existing sellers keep their current value, including2; clear a store's lead
time to move it to the marketplace default. If you ran the migration on a
v2.3.9canary, it cleared values of2, so those sellers need them set again. - Offers honour the product–seller allowlist (#1697).
createOffersWorkflownow refuses a product restricted to other sellers or
rejected by the operator. Products with no link stay open to every seller. - Store order groups no longer return
orders.items.variant.product(#1727).
Asking for it explicitly returns 400. Useorders.items.product_handleand
orders.items.product_title. GET /vendor/paymentsscopes through the order's payment collection
(#1678), so shared, uncaptured cart-stage payments are not listed.- Storefront on Next 16 (#1713). Catalog prices are fetched anonymously, so
customer-group price lists no longer show on catalog pages (the cart still
applies them). Canonical and structured-data URLs now come from
NEXT_PUBLIC_BASE_URL. - Mercur now sends anonymous telemetry from your server (#1718, #1725). It's
on by default and prints nothing at boot, and it helps us see how many
installations exist, which versions they run and whether they keep running.- Server: a startup event and a daily heartbeat with a random installation
id, versions, runtime, OS and infrastructure details, feature flags, and
one-time milestones such as the first approved seller or first paid payout.
Marketplace counts are sent as range buckets, never exact numbers. Only
first-party plugins are named; custom ones are just counted. Each event also
says whether the install was seeded with demo data. - CLI: every command now reports its name, flag names, exit code and
duration, with flag values redacted unless they're a boolean or a fixed
choice. It stops sendingblocks.jsoncontents,aliasPrefix, CPU model and
email, and the email prompt is gone. - Opt out with
MERCUR_DISABLE_TELEMETRY,DO_NOT_TRACKor
withMercur({ projectConfig: { mercur: { telemetry: false } } }). It's also
off in CI, underNODE_ENV=testand in worker processes. Set
MERCUR_TELEMETRY_DEBUG=trueto print payloads instead of sending them.
Every event and property is listed on the telemetry docs page.
- Server: a startup event and a daily heartbeat with a random installation
✨ Highlights
💳 Stripe connector plugin
Core payouts are now shaped like Medusa's payment module: one provider per
marketplace, one payout per seller order, and one payout_reversal per later
refund. An order that isn't ready goes on_hold and is re-decided on the next
run. Core still schedules nothing. @mercurjs/stripe-connector-plugin brings
the operations: seven scheduled jobs, settlements that net payouts, reversals
and adjustments per seller and currency, operator adjustments, capture
validation and failed-operation alerts. Operators get Integrations → Stripe and
Settings → Stripe, plus widgets on the seller and order pages. Vendors get their
own Stripe page. (#1693)
🛒 Magento 2 connector
@mercurjs/magento2-connector-plugin lets a Magento 2 storefront sell
marketplace offers while Mercur keeps sellers, split orders, commissions and
payouts. It ships HMAC-signed webhooks with retries and redelivery, and an
external payment provider that authorizes at checkout and captures when Magento
invoices the order. Storefront refunds are mirrored onto the Mercur order, and
there's an admin screen at /settings/magento. The Magento side is the separate
mercurjs/magento2-connector Composer package. (#1683, c5305c2)
🌍 Sellers in regions
Operators decide which regions a seller operates in, from the region page, the
seller page or Create Seller. Vendors see and price only in their assigned
regions, and the offer grids get one column per region. Admin keeps full
visibility of prices across all regions. New sellers get the store's default
region. (#1665)
🧱 Layout composer and deeper panel extensions
Admin product detail and vendor offer detail now render through a
LayoutComposer that uses Medusa's entry ids. Plugins, the Medusa project and
the host app are ordered layers where later wins, and any layer can contribute
routes, widgets, layouts/, _layouts.ts and _navigation.ts (#1674).
Custom-fields configs can also hide, replace or add columns and fields on
embedded tables, spreadsheet grids and built-in form fields, using the existing
list and forms keys (#1701).
🧭 Customizable vendor onboarding
Each onboarding step is now a tab of the seller onboarding form zone, so you
can add, remove, reorder or mark steps optional from a custom-fields config. The
store handle is now required, with a live check against the new
GET /vendor/sellers/handle-availability. Going back after a failed Finish no
longer wipes what the user typed. (#1658)
🏷️ Offer conditions in the panels
Operators manage conditions under Settings → Offer Conditions, and vendors pick
one when creating or editing an offer. (#1657, #1661)
⏱️ Lead time defaults
Lead time gets its UI and a marketplace-wide default, set in Settings →
Marketplace, on the store, and on the offer. GET /store/offers returns
effective_leadtime_to_ship, resolved from offer to store to marketplace.
(#1687)
⚡ Performance
- Fix checkout lock units: timeouts passed in milliseconds meant a lock wait of
about 8 hours and a TTL of about 33. Concurrent completion under
processPaymentWorkflowis now locked too (#1677). - Load the commission rate table once per checkout, and add list indexes on
order groups, offers, payouts, reviews and commission rules (#1677). - Push vendor list seller scoping into SQL
EXISTSsubqueries instead ofid IN
lists that grow with lifetime volume (#1678).
🛍️ Storefront
- Move to Next 16 with Cache Components: every route has a prerendered shell,
the default first page of a listing sits in the HTML, and catalog reads are
cached (#1713). - Add a cart drawer that opens instantly, optimistic cart edits, and a quantity
picker on the product page. Pages no longer wait on the cart (#1711).
🧩 More extension points
offerPricesUpsertedhook, plus previous values, snapshots andupdated_by
on the other offer hooks (#1654).getStatisticson the seller and offer modules, with
GET /admin/sellers/statisticsandGET /admin/offers/statistics(#1659).deleted_atfilter andwith_deletedon admin offers and sellers, so a delta
sync can see removals (#1683).order_group.metadata, copied fromcart.metadata(#1683).POST /store/carts/:id/completenow returns the seller orders (#1697).is_replayon thepaymentRefundedhook (#1685).
Platform
- Bump Medusa to 2.22.0 (#1685).
🐛 Fixes
- Allocate cart credit lines across split orders, so the credit-covered part no
longer shows as owed. Fully credit-covered orders still stay on hold for
payout (#1728). - Compensate commission lines, payment captures and links when checkout is
canceled after completion, so no marketplace records stay behind (#1729). - Confirm offer stock against the cart's total demand per inventory item, so
buyers learn at add-to-cart rather than at checkout (#1668). - Subtract reserved stock from availability on
GET /store/offers(#1669). - Stop Edit prices on a vendor offer from wiping every price when saved before
regions loaded (#1673). - Fix in-place updates of quantity-tiered offer prices never completing (#1654).
- Return
items.offeranditems.offer.selleron store cart routes, so carts
group by the right seller (#1712). - Stop
GET /store/order-groupsreturning 400 withoutfields, which crashed
the storefront's order page (#1727)...
v2.3.7
Mercur v2.3.7
A smaller release with one new building block and a batch of fixes. Offers
learn about conditions, so operators can define "new", "refurbished" or "used"
and sellers pick one per listing. The admin order list gets a payment status
filter, server-side search in its filters and smarter search by order number.
Two security fixes close cross-seller gaps in the vendor API and in cart
promotions, and payouts no longer call the provider before a row is written.
⚠️ Before you upgrade
- Run migrations — the offer module adds an
offer_conditiontable and
offer.condition_id(seeded with a defaultnewcondition), and the payout
module widens thepayout.statuscheck constraint to acceptprocessing
(#1645, #1641). - Payouts are briefly
pending.createPayoutsnow inserts the row
before calling the provider, sopayout.createdfires for a pending row and
readers that assumed every row is already sent should checkstatus(#1641). - Seller promotions are scoped on every cart refresh. A vendor promotion
without seller-specific target rules, or an automatic one, no longer
discounts other sellers' items. If your storefront relied on that, it was a
bug (#1648).
✨ Highlights
🏷️ Offer conditions
Operators define the physical conditions an offer may be listed under through
/admin/offer-conditions (code, label, is_active, rank), and sellers pick
an active one via condition_id when creating or updating an offer. Admin,
vendor and store offer responses expose condition.{id,code,label} and filter
on condition_id. Deleting a condition in use is refused; deactivating keeps
existing offers but blocks new assignments. API only for now, with
createOfferConditionsWorkflow / update / delete hooks and
offer_condition.* events. (#1645)
📋 Admin order list
- Filter order groups by payment status. The status is computed from
payment collections, soGET /admin/order-groups?payment_status=captured
matches exactly what the list shows (#1639). - Search by the numbers operators actually see:
264,#264or#G258now
match the group's display id or any child order's display id (#1638). - Customer, Store and Sales channel filters search on the server, so stores
with more than 1000 customers or sellers can select any of them (#1640).
🔒 Security
Both issues below were reported privately by @gregbarajas, with clean
end-to-end reproductions on a v2.3.6 starter that made the fixes
straightforward. Thank you, Greg!
- Close cross-seller authorization gaps in the vendor API:
fieldstraversal
into other sellers' members, payment details and invite tokens, product
detail routes on another seller's drafts, capture of shared cart payments,
and offer / inventory body ids pointing at another seller's items or
locations (GHSA-mhch-gq7f-v5p7, #1649). - Keep seller promotions scoped to the seller's own items on every cart
refresh, not only when applied throughPOST /store/carts/:id/promotions
(GHSA-w4vq-6jvh-cfpg, #1648).
🐛 Fixes
- Insert the payout row before calling the provider, use the payout id as the
defaultidempotency_key, and accept theprocessingstatus in the
database (#1641). - Intersect
idandseller_idfilters onGET /admin/order-groupsinstead
of returning every group (#1637). - Rebuild the Variants table on every attribute change in Products → Create in
both panels, and surface create errors as a toast instead of a silent 400
(#1650).
Full Changelog: v2.3.6...v2.3.7
v2.3.6
Mercur v2.3.6
A big one. Split orders finally get payments of their own, so refunds and
cancellations work through the stock Medusa routes. Product descriptions get
a rich text editor, and the sidebar collapses to an icon rail. We bumped
Medusa to 2.21.2 and moved to zod 4 — plus a long list of new extension
points and fixes.
⚠️ Before you upgrade
- Medusa 2.21 and zod 4 (#1554, #1614). Every
@medusajs/*pin moves to
2.21.2 and the workspace runs on zod 4. In your own validators, rewrite
single-argumentz.record(value)toz.record(key, value). - The built-in notifications bell is gone (#1572).
Notificationsis no
longer exported from@mercurjs/adminor@mercurjs/vendor, and the
Cmd/Ctrl+N shortcut is removed. Register your own widget on the new
topbar.afterzone instead. - Panels sign in with bearer tokens (#1631). Both panels drop the cookie
session, so everyone is logged out once after upgrading. Custom page
clients should sendgetAuthHeaders()from@mercurjs/admin/
@mercurjs/vendorinstead ofcredentials: 'include'. - Admin product delete goes through the change pipeline (#1583).
DELETE /admin/products/:idnow returns an error when the product has a
pending product change. - Run migrations —
PayoutAccount.datagets a default (#1582).
✨ Highlights
💳 Per-order payments for split orders
When a cart's payment is captured, it is now split: every seller order gets
its own payment collection with a captured payment for its share, pointing at
the same charge. Refunding and canceling a split order work through the stock
Medusa routes and workflows, capped at the order's share. Canceling before
capture leaves the authorization for the cart's other orders. Orders captured
before this release are not split retroactively. (#1627)
✍️ Rich text product descriptions
Product descriptions can mix formatted text and images. RichTextEditor and
the RichText renderer ship in @mercurjs/dashboard-shared and are used
across product create, edit and detail in both panels. Descriptions are
sanitized on write and on the storefront; existing plain-text descriptions
keep working, and there is no migration. (#1584)
🧭 Sidebar: icon rail and nav groups
The sidebar collapses to an icon rail with tooltips instead of disappearing,
and useSidebar / useCollapsedSidebar let a page drive it. _navigation.ts
gains a groups API, so you can declare groups and assign items to them in
both the main and settings sidebars. (#1559, #1586, #1621, #1629)
📝 Extendable registration and invitation
A plugin can add fields to the vendor register and invite forms and act on
them on the server: createSellerAccountWorkflow gets a validate hook,
and acceptMemberInviteWorkflow gets validate and memberInviteAccepted,
with additional_data accepted on invite acceptance. Plugin vendor routes
exporting AUTHENTICATE = false now skip vendor auth. (#1591, #1613)
🧩 More extension points
paymentCapturedandpaymentRefundedhooks on the capture and refund
workflows, plusreturnReceivedandcommissionLinesUpserted(#1558).payoutCreatedhook oncreatePayoutWorkflow(#1580).fulfillmentDeliveredhook onmarkOrderFulfillmentAsDeliveredWorkflow
(#1607).topbar.before/topbar.afterwidget zones in both panels (#1572).- Extensions can add tabs to the admin store detail page via
stores.detail.tabs.*and alabelondefineWidgetConfig(#1609). - Per-card
locations.list.itemwidget zone on the locations list (#1620). - Custom-fields list filters can replace or remove built-in filters (#1634).
- Deleting a product now soft-deletes its seller offers (#1583).
- Offer inventory items inherit the variant's shipping and customs
attributes (#1624).
Platform
🐛 Fixes
- Keep admin and vendor logins independent when both panels share one API
host (#1631). - Return the existing order group when a cart is completed again, instead of
placing duplicate orders (#1603). - Preserve other sellers' prices when one seller updates an offer price
(#1550). - Restore Medusa middlewares on product routes Mercur does not override —
fixes 500s on variant batch, options and product-variant routes (#1553). - Stop showing a published, allowlisted product to its creator once it is
restricted to other stores (#1552). - Keep order group
seller_countandtotalindependent of list filters
(#1632), and make sorting bydisplay_idwork (#1633). - Forward order list filters to the API in the vendor panel (#1556).
- Apply Medusa patches on import, so blocks imported in
medusa-config.ts
see the patched workflows (#1573). - Return a proper 400 instead of a 500 when a payout account is created
withoutdata(#1582). - Scaffold new projects from the template of the pinned release tag, so
create-mercur-appno longer produces projects that fail to boot (#1623). - Resolve the local CLI directly for codegen — no more slow
npxon every
boot in monorepos (#1619). - Detect Medusa's Stripe card provider on the storefront (#1581) and
self-host the Funnel Display font (#1611). - Restore dropped placeholders and
Transtags in admin and vendor locales
(#1612). - Use the outline star for the vendor reviews sidebar item (#1608).
- Resolve the repository root dynamically in the dev scripts (#1618).
- Bump
sanitize-htmlto 2.17.7 (#1625).
🙌 New contributors
- @theluckystrike made their first contribution in #1612 — thank you!
- @mahdi2k1 made their first contribution in #1618 — thank you!
Full Changelog: v2.3.5...v2.3.6
v2.3.5
Mercur v2.3.5
A patch release focused on security. We closed three authorization gaps in the
Store and Vendor APIs and patched critical Next.js advisories in the
storefront. Plugins also get new seams on the order detail page, plus a fix for
the vendor onboarding currency select.
⚠️ Before you upgrade
POST /store/carts/:id/line-itemsno longer acceptsunit_priceor
compare_at_unit_price(#1546). Requests that send either field now get a
400. Line prices always come from the offer, so remove these fields from
any storefront or integration that sends them.
🔒 Security issues addressed
- Client-supplied cart prices. A shopper could set
unit_pricewhen adding
a line item. The price was treated as custom, skipped recalculation and
carried into the order and payment authorization. These fields are now
rejected. (#1546) - Cross-product variant edits.
POSTandDELETE /vendor/products/:id/variants/:variant_iddidn't check that the variant
belonged to the product in the path. A change request could update or delete
a variant (including its images) on a different product. Both routes now
return404when the variant isn't on that product. (#1545) - Customer group membership.
POST /vendor/customer-groups/:id/customers
checked that the seller owned the group but not the customers. A vendor could
add or remove any customer on the platform, including other sellers'
customers. EveryaddandremoveID is now checked, and the route returns
404if any customer isn't linked to the seller. (#1544) - Next.js RCE advisories in the storefront. Bumped
nextto 15.5.24 in
apps/storefront. This resolves GHSA-2xp9-vwfh-vxw4 (unauthenticated RCE in
the Image Optimization API with AVIF files) and GHSA-p293-qw3h-jr36
(unauthenticated RCE on Windows-hosted servers). If your storefront is based
on ours, bumpnextthere too. (#1542)
We recommend all marketplaces upgrade.
✨ Highlights
🧩 Order detail extension points
Plugins such as disputes can now act on an order without forking its sections.
This works in both panels. (#1530)
- Action locks.
actionLocksindefineCustomFieldsConfigcan disable
Edit order, Return, Exchange and Claim. The string you return becomes
the disabled action's tooltip. - Line-item zone. A new
orders.detail.itemwidget zone on each summary
row receives{ order, item }. - Timeline entries.
activityadds your own entries to the order activity
timeline.
Nothing changes unless a plugin configures these.
🐛 Fixes
- Return supported currencies from
GET /vendor/stores, so the vendor
onboarding Currency select is populated again on Medusa 2.20 (#1543)
Full Changelog: v2.3.4...v2.3.5
v2.3.4
Mercur v2.3.4
This release is all about extensibility. Commissions get pluggable providers,
split orders and product edits get new workflow hooks, and offers learn about
handling time. Shipping profiles become operator-owned, RBAC is now fully
opt-in, and we bumped Medusa to 2.20.1 — plus a batch of fixes across offers,
the panels and the client.
⚠️ Before you upgrade
- Shipping profiles are now operator-owned (#1498). Only the marketplace
operator defines shipping profiles; vendors can still read and select them.
POST/DELETEon/vendor/shipping-profilesare removed, along with the
seller_idfilter onGET /admin/shipping-profilesand the
shipping-profile-seller-link. - Run migrations — new columns on offers, sellers and commission lines.
- RBAC is opt-in (#1521). If you use seller roles and permissions, enable
it withwithMercur({ featureFlags: { rbac: true } }).
✨ Highlights
💸 Commission providers
The commission module now has a provider seam, just like payouts. Implement
ICommissionProvider to plug in your own commission logic, and pass extra
context through the new setCommissionContext hook. The existing calculation
ships as the default system provider, so nothing changes unless you
configure one. (#1509)
📦 Order acceptance flows
A new setOrderStatus hook on completeCartWithSplitOrdersWorkflow lets a
plugin set each seller order's initial status — for example requires_action
for marketplaces where sellers accept orders first. Both panels now lock
fulfil, edit, return, exchange, claim, capture and refund on those orders, and
order tables get a status filter. (#1513)
🚚 Handling time on offers
Offers accept leadtime_to_ship — business days until the parcel is handed to
the carrier — falling back to the seller's default_leadtime_to_ship
(default 2). The building block for shipping deadlines and delivery
estimates. (#1491)
🧩 More extension points
validatehook on the vendor product-edit workflows (#1484).value_idsaccepted on product attribute batch update (#1481).ClientErrorkeeps the API errorcode, so your storefront can react to a
specific refusal instead of matching on the message (#1477).
Platform
- Bump Medusa to 2.20.1 (#1489).
🐛 Fixes
- Reject offer prices of
0or less, and skip unpriced variants in the vendor
Create offers form (#1522). - Accept a decimal comma in offer price fields (#1520).
- Stop marking unhandled product-change actions as applied (#1463).
- Tolerate dangling price links in
updateOffersWorkflow(#1482). - Merge plugin i18n into the panels and keep parent breadcrumbs on file-based
routes (#1511). - Use valid BCP 47 tags for Chinese and Portuguese (#1519).
- Stub
virtual:medusa/search-entitiesin the dashboard SDK (#1495). - Avoid a 4-level field expansion when the storefront retrieves the cart (#1518).
📚 Docs
- Clarify the Stripe webhook events needed for split-order checkout (#1523).
🙌 New contributors
Full Changelog: v2.3.3...v2.3.4
v2.3.3
🐞 Bug Fixes
View changes on GitHub
v2.3.2
v2.3.2
A security and hardening release. All marketplaces running 2.3.1 or earlier should upgrade.
🔒 Security
This release fixes a critical broken-access-control (BOLA/IDOR) vulnerability in the vendor API, privately reported by @hackchang under coordinated disclosure. Thank you.
The issue: the /vendor/sellers/:id route subtree operated on the URL id parameter without binding it to the authenticated seller. Any authenticated vendor could target another seller's id to read that store's full record (profile, payment details, professional/tax details), overwrite its profile, and manage its team members — invite themselves in, change roles, remove members. In effect, full account takeover of any other vendor's store.
Weakness: CWE-639 / CWE-862 · Severity: Critical (CVSS 3.1 ~9.3, AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
What changed:
- Every
/vendor/sellers/:idhandler is now scoped to the authenticated seller; a mismatched id no longer resolves (#1449) - Member-management routes bind
member_idto the caller's own seller GET /vendor/storesnow requires member authentication (#1450)- The vendor stores query has its selectable fields restricted (#1453)
A GitHub Security Advisory with a CVE request is being published from the repository's Security tab, with @hackchang credited as reporter.
✨ Features
- Attribute edits now go through the product-change diff pipeline, the same way product edits do (#1444)
- CLI builds admin dashboard extensions, and a package can ship its own entry point (#1424)
create-mercur-apppins generated projects to the CLI's release channel (#1459) — scaffolding with@rcno longer mixes inlatestpackages
🐛 Fixes
Checkout & orders
- Core owns the payment webhook so carts complete as split orders (#1418)
- Split-order payment status is reported correctly on the admin and store order routes (#1458)
- Payment statuses are no longer double-counted (#1399) — thanks @shin4141
- Cart shipping profiles are validated against the offer (#1417), and shipping-method cleanup is derived from offers rather than products (#1455)
Offers & attributes
- Created offer prices are attributed by rule instead of position (#1397)
- New scoped-axis option values are associated with their product (#1454)
- Attribute possible-values pagination fixed (#1447) — thanks @adeaviladev01
- The pending product-change guard is actor-scoped (#1443)
Dashboards
RouteDrawerdialogs have an accessible title (#1415)- The admin invite form carries the roles field, so invited admins aren't locked out (#1420)
DataGridcolumn resizing works (#1452)- The
NoRecordsempty state grows instead of overflowing (#1456) react-router-domis a peer dependency (#1426)dashboard-sdkresolves plugin roots via Node and no longer emits top-levelawait(#1429)plugin:buildno longer races itself forsrc/admin/index.mjs(#1427)
Other
- Algolia block: the five dead
algolia.intermediate.*subscribers removed (#1416) — thanks @Roshan931 - Storefront drops the unused
next-intldependency (#1457) - Architecture docs rewritten around layers and extension zones (#1412)
📦 Upgrading
npm install @mercurjs/core@2.3.2Bump every @mercurjs/* package to 2.3.2 together — they are released in lockstep. Newly scaffolded projects from templates/basic pin to 2.3.2 automatically.
New Contributors
- @shin4141 — #1399
- @Roshan931 — #1416
- @adeaviladev01 — #1447
- @hackchang — security report (no PR)
Full Changelog: v2.3.1...v2.3.2
v2.3.1
v2.3.1
A patch release with bug fixes across the vendor panel, dashboard SDK, storefront, and scaffolding CLI.
Fixes
- vendor: Split orders now show the authorized payment status correctly (#1390)
- dashboard-sdk: Register the
layoutsandcell-renderersvirtual modules so they resolve at build time (#1388) - storefront: Bump
@mercurjs/*deps to the release version to fix hoisted stale types breakingmedusa build(#1393) - create-mercur-app: Pin
packageManagerso Turborepo can resolve the workspace in freshly scaffolded projects (#1389)
Chores
- storefront: Remove
vercel.json(#1392)
Full changelog: v2.3.0...v2.3.1
v2.3.0
Mercur v2.3.0
This release brings a wave of UI polish across the Admin and Vendor panels, two
brand-new Reviews surfaces, a smarter offer inventory system, fully revamped
documentation, and a bump to Medusa 2.18.0 — plus a batch of fixes reported by
the community.
✨ Highlights
📚 Revamped documentation
Our docs got a major overhaul — restructured into clear Platform, Build,
and Reference sections, with refreshed positioning and new User Guides
to walk you through the platform step by step. Check them out at
https://docs.mercurjs.com. (#1340, #1355)
Reviews
- Admin reviews panel + core review module — moderate product and seller
reviews from the operator dashboard, backed by a dedicated review module. (#1309) - Vendor reviews — sellers can now see and manage reviews from their own
panel. (#1308)
Offers & Inventory
- Manage inventory, inventory kits & backorders for offers — offer-scoped
inventory management, multi-item kits, and backorder support. (#1336)
Panel redesigns
- Admin: redesigned Inventory (#1329), Reservations (#1330) and Price Lists (#1331) panels.
- Vendor: redesigned Inventory (#1332), Reservations (#1334) and Price Lists (#1338) panels.
- Campaigns: admin Campaigns UI refresh with list filters (#1307); vendor
Campaigns refresh (#1305) plus detail polish, promotions filters & a filtering
refactor (#1316). - Admin login screen redesigned (#1306).
Platform
- Bump Medusa to 2.18.0 (#1368).
🐛 Fixes
- Declare zod as a runtime dependency in core (#1310).
- Unblock codegen crash from execa
Set.prototype.union(#1311). - Guard undefined regions in the shipping option pricing form (#1327).
- Resolve sidebar labels through
translationNs(#1369). - Make DataGrid columns resizable (#1370).
- Point storefront hreflang alternates at 200 locale URLs (#1356).
- Use the built-in password reveal on the login field (#1314).
- Default campaigns list to
created_atorder (#1315).
🧰 Internal / Chore
- Self-contained UI e2e harness (#1312) and fixture lint fix (#1313).
- Rename storefront package to
@mercurjs/storefront(#1301). - README star-history snapshot + docs home video tweaks (#1337, #1342, #1343, #1354).
🙌 Thanks to the community
Huge thanks to everyone who reported issues, shared feedback, and tested canary
builds — Mercur gets better because of you.
Welcome and thank you to our new contributor @bansal-ashish-dev, who made
their first contribution in #1327! 🎉
Full Changelog: v2.2.1...v2.3.0