medusa-plugin-quote
Adds customer-requested quotes to Medusa v2, so a customer can ask for a price on their cart and turn the merchant's offer into a real order.
A customer asks for a price on their cart; the merchant amends and offers; the customer accepts or turns it down. Accepting produces a real order at the agreed prices.
When to use
Install it in a Medusa backend for a storefront where customers negotiate prices, typically B2B. It works on its own and does not need the company plugin. A shop with fixed prices only does not need it.
Get started
Install the package with the peer dependencies it needs. It comes from the Evolve registry, which needs a one-time sign-in.
pnpm add @evolve-framework/medusa-plugin-quote @medusajs/cli @medusajs/framework @medusajs/medusaRequirements4
node>=20@medusajs/cli2.21.2@medusajs/framework2.21.2@medusajs/medusa2.21.2
For example:
// medusa-config.ts// After adding the plugin, run `npx medusa db:migrate` to create its tables.import { defineConfig } from "@medusajs/framework/utils";
module.exports = defineConfig({ // projectConfig, modules and other plugins as before plugins: [ { resolve: "@evolve-framework/medusa-plugin-quote", options: {} }, ],});How it works
The quote record holds almost nothing. What is being negotiated lives in a draft order, and the merchant's proposal lives in an order edit on it. Both are Medusa's own machinery. So the arithmetic of prices, taxes, promotions and shipping is the same code that computes any other order, the preview a customer is shown is produced by the code that will produce the order, and accepting is just confirming the edit. There is no second copy of the prices to disagree with the first.
The cart is left alone throughout. A shopper who asks for a quote has not stopped shopping.
Statuses
pending_merchant → pending_customer → accepted, with
customer_rejected and merchant_rejected as the ways out. A customer
rejection is not a dead end: the draft order survives, so the merchant can amend
and send again. An accepted quote is an order in progress and can no longer be
rejected by either side.
Routes
Store, for the signed-in customer, limited to their own quotes:
GET /store/quotes |
their quotes |
POST /store/quotes |
ask for one, naming a cart_id and optionally a purchase_order_number |
GET /store/quotes/:id |
one, with the draft order |
GET /store/quotes/:id/preview |
the order as it would be if accepted |
POST /store/quotes/:id/accept |
take the offer |
POST /store/quotes/:id/reject |
turn it down |
POST /store/quotes/:id/messages |
say something |
Admin, for the merchant, across all quotes: GET /admin/quotes,
GET /admin/quotes/:id, and POST /admin/quotes/:id/send, /reject and
/messages. What is offered is whatever is staged on the draft order's open
edit, so send carries no body: the proposal already exists, and sending only
hands the turn over.
Like Medusa's own admin routes, these accept an admin session, a bearer token or a secret API key sent with HTTP Basic authentication. A remark posted with an API key names the key's id as its author.
A quote id appears in emails and order history, so it is never the only thing standing between a caller and someone else's negotiated prices: a quote that is not yours reads as absent, not as forbidden.
Attribution
Ported from the Medusa B2B Starter (MIT), which is the reference for the model, the read-only links to the draft order and order change, and the workflow wiring. That is particularly the cart → draft order → order edit → order sequence, which is the part worth having.
Differences: useRemoteQueryStep is deprecated in 2.18 and is replaced with
useQueryGraphStep throughout; the starter's update-order workflow is renamed
quote-update-order, because workflow ids are global and a plugin should not
claim a name that generic; and the Store API routes are written for the Evolve
GraphQL contract rather than ported from the starter's Next.js storefront.

