Cart Rules
Cart and per-product quantity limits
@xross-dev/medusa-cart-rules
Medusa v2 plugin. Blocks add-to-cart, quantity increase and checkout when a quantity rule is violated.
Rules
Copy to clipboardrule_type Meaning Copy to clipboardmax_cart_quantity Total items ≤ limit. Scope Copy to clipboardglobal = whole cart; other scopes = combined items matching the scope (e.g. "max 4 items from collection X"). Copy to clipboardmax_product_quantity Per product ≤ limit (per variant when scope = Copy to clipboardvariant). Scope Copy to clipboardglobal = every product; Copy to clipboardproduct/Copy to clipboardcollection/Copy to clipboardcategory = matching products.
Audience: everyone (+ optional guests), or one customer group. Also: sales channel, active window (Copy to clipboardstarts_at/Copy to clipboardends_at), priority, custom message (Copy to clipboard{limit} {current} {product}).
Resolution
- Per-product: variant > product > collection/category > global (first tier with a matching rule wins).
- Same scope target: a customer-group rule overrides the "everyone" rule (VIP 50 beats global 20).
- Several matching group rules: Copy to clipboard
restrictive(lowest, default) or Copy to clipboardpermissive(highest). - Cart-level and product-level rules are checked independently; all must pass.
- Decreasing a quantity is always allowed. Add/update only block for products they touch; Copy to clipboard
completechecks the whole cart.
Install
1234# in the pluginyarn && yarn build # migration for cart_rule + cart_rule_violation is already included# (after changing models: yarn db:generate, needs a local Postgres + DB_USERNAME/DB_PASSWORD)npx medusa plugin:publish # or: yalc / npm publish
1234// medusa-config.tsplugins: [{ resolve: "@xross-dev/medusa-cart-rules", options: { conflict_strategy: "restrictive" } },],
1npx medusa db:migrate # in the app
Dev loop: Copy to clipboardnpx medusa plugin:develop in the plugin, Copy to clipboardnpx medusa develop in the app.
API
Admin (auth required): Copy to clipboardGET|POST /admin/cart-rules, Copy to clipboardGET|POST|DELETE /admin/cart-rules/:id, Copy to clipboardGET /admin/cart-rules/violations, Copy to clipboardPOST /admin/cart-rules/simulate Copy to clipboard{ cart_id? , customer_id?, items?: [{variant_id, quantity}] }.
Store (publishable key): Copy to clipboardGET /store/cart-rules?cart_id=…[&variant_id=…] or Copy to clipboard?variant_id=…
123{ "limits": [{ "rule_id": "crule_…", "rule_type": "max_cart_quantity", "scope_type": "global", "limit": 20, "current": 12, "remaining": 8 }],"lines": { "item_…": { "remaining": 2, "max_quantity": 5 } },"variant": { "variant_id": "variant_…", "remaining": 3 } }
Use Copy to clipboardlines[id].max_quantity to cap the quantity stepper.
Blocked requests return HTTP 400 Copy to clipboard{ "type": "invalid_data", "code": "CART_RULE_VIOLATION", "message": "You can add at most 5 of Phone." }.
Custom rule types
12import { registerRuleType } from "@xross-dev/medusa-cart-rules/modules/cart-rules/engine"registerRuleType("max_cart_total", { label: "...", defaultMessage: "...", measure: (cart, rules, ctx) => [/* Measurement[] */] })
Call it from a loader or any file loaded at boot. The engine (Copy to clipboardengine/) is pure (no I/O) and unit-tested: Copy to clipboardyarn test.
Notes
- Workflow hooks used: Copy to clipboard
addToCartWorkflow, Copy to clipboardupdateLineItemInCartWorkflow, Copy to clipboardcompleteCartWorkflow→ Copy to clipboardhooks.validate. - Scope/group/channel IDs are plain strings (no module links), validated on create/update.
- Admin page: Cart Rules in the sidebar (rules CRUD + blocked-attempts log), built with Copy to clipboard
@medusajs/ui.
