Skip to content

feat(shipping): weight-based shipping with weight_total totals and conditional price rules - #16799

Draft
pmwheatley wants to merge 7 commits into
medusajs:developfrom
pmwheatley:feat/wbs
Draft

feat(shipping): weight-based shipping with weight_total totals and conditional price rules#16799
pmwheatley wants to merge 7 commits into
medusajs:developfrom
pmwheatley:feat/wbs

Conversation

@pmwheatley

@pmwheatley pmwheatley commented Sep 11, 2026

Copy link
Copy Markdown

Summary

What — What changes are introduced in this PR?

Adds weight-based shipping support to Medusa:

  • Cart model: new nullable unit_weight column on cart_line_item (migration Migration20260911100000)
  • Totals: weight_total on line item totals (unit_weight × quantity) and cart totals (Σ line items), exposed as weight_total / raw_weight_total on CartLineItemDTO and CartDTO
  • Pricing rules: weight_total accepted as a shipping option price rule attribute in the admin API (alongside item_total), participating in calculated-price context when listing cart shipping options
  • Admin dashboard: weight_total option in the conditional price form for shipping options, with range-overlap validation and i18n labels for all locales
  • Docs: weight_total rule attribute and totals documented in the pricing price-rules and cart totals references

Why — Why are these changes relevant or necessary?

Merchants currently can't price shipping by cart weight, a standard carrier requirement. Shipping prices can only condition on item_total. This adds weight as a first-class conditional dimension, computed automatically from line item weights (a product variant's weight is carried onto cart line items as unit_weight). Resolves #16800.

How — How have these changes been implemented?

  • unit_weight persisted through prepareLineItemData in cart workflows, inherited from variant.weight ?? product.weight; aggregated by the totals utils (@medusajs/utils) using BigNumber-safe math
  • weight_total added to the cart query fields (core-flows) and store cart query config so it reaches the price-calculation context in listShippingOptionsForCartWithPricing
  • Admin validator whitelists weight_total in the price-rule attribute enum (z.enum(["item_total", "weight_total"]))
  • Dashboard: conditional-price-form gains a weight condition type; price-rule-helpers handle weight range normalization and overlap detection across mixed item_total/weight_total rules

Testing — How have these changes been tested, or how can the reviewer test the feature?

  • Unit: packages/core/utils/src/totals/__tests__/totals.ts (weight_total calculation, zero default), packages/medusa/src/api/admin/shipping-options/__tests__/validators.spec.ts (attribute whitelist, operator/value validation), dashboard price-rule-helpers.spec.ts
  • Integration: integration-tests/http/__tests__/cart/store/cart.spec.ts (weight rule applied at checkout, AND semantics with item_total, store listing filter, exclusion case), integration-tests/http/__tests__/shipping-option/admin/shipping-option.spec.ts (create with weight_total rules, price_total/unknown attributes rejected with 400), cart module service spec (weight_total in totals)
yarn build
yarn jest packages/core/utils/src/totals packages/medusa/src/api/admin/shipping-options
cd integration-tests/http && yarn test:integration shipping-option/admin
cd integration-tests/http && yarn test:integration cart/store -t weight_total

Examples

Creating a flat shipping option with a weight-based conditional price (via the JS SDK):

const { shipping_option } = await sdk.admin.shippingOption.create({
  name: "Standard Shipping",
  service_zone_id: "serzo_123",
  shipping_profile_id: "sp_123",
  provider_id: "manual_test-provider",
  price_type: "flat",
  type: { label: "Standard", description: "Standard shipping", code: "standard" },
  prices: [
    // default price
    { currency_code: "usd", amount: 10, rules: [] },
    // price if cart weight is 5000 or more
    {
      currency_code: "usd",
      amount: 15,
      rules: [{ attribute: "weight_total", operator: "gte", value: 5000 }],
    },
  ],
})

The cart's weight_total is the sum of its line items' weights, where each line item's weight is its unit_weight multiplied by its quantity. unit_weight is inherited from the weight property of the product variant.


Checklist


Additional Context

The branch is staged as small, isolated commits: types/model/migration + red unit tests → implementation → integration tests → dashboard UI → docs → changeset.

We've been maintaining this feature internally across several Medusa versions now, and now propose it as a logical addition to the core. The implementation pattern follows that of the existing item_totals-based conditional pricing work as closely as possible, and would make a valuable addition to Medusa.

- add unit_weight column to line item model, types and migration
- add weight_total to cart totals types
- whitelist weight_total as shipping option price rule attribute
- unit tests for totals and validators (failing until implementation)
- compute weight_total in cart and line item totals
- persist unit_weight through line item preparation
- expose weight_total when listing cart shipping options
- allow weight_total price rule attribute in admin validators
- checkout flow applies weight_total price rules (store carts)
- weight_total and item_total rules combine with AND semantics
- listing store shipping options filters by weight_total
- admin api accepts weight_total rules and rejects removed price_total
- cart module service returns weight_total
- weight_total option in conditional price form with overlap validation
- price rule helpers and form schema for weight conditions
- translations for weight-based pricing labels
- document weight_total as a price rule attribute with examples
- explain how unit_weight is inherited from the product variant
- add weight_total and unit_weight to cart totals reference
@pmwheatley
pmwheatley requested review from a team as code owners September 11, 2026 13:22
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6564486

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 83 packages
Name Type
@medusajs/medusa Minor
@medusajs/cart Minor
@medusajs/core-flows Minor
@medusajs/utils Minor
@medusajs/types Minor
@medusajs/dashboard Minor
@medusajs/test-utils Minor
@medusajs/loyalty-plugin Minor
@medusajs/medusa-oas-cli Minor
integration-tests-http Patch
@medusajs/event-bus-redis Minor
@medusajs/framework Minor
@medusajs/modules-sdk Minor
@medusajs/orchestration Minor
@medusajs/query Minor
@medusajs/workflows-sdk Minor
@medusajs/cli Minor
@medusajs/draft-order Minor
@medusajs/js-sdk Minor
@medusajs/admin-bundler Minor
@medusajs/analytics Minor
@medusajs/api-key Minor
@medusajs/auth Minor
@medusajs/caching Minor
@medusajs/currency Minor
@medusajs/customer Minor
@medusajs/file Minor
@medusajs/fulfillment Minor
@medusajs/index Minor
@medusajs/inventory Minor
@medusajs/link-modules Minor
@medusajs/locking Minor
@medusajs/notification Minor
@medusajs/order Minor
@medusajs/payment Minor
@medusajs/pricing Minor
@medusajs/product Minor
@medusajs/promotion Minor
@medusajs/rbac Minor
@medusajs/region Minor
@medusajs/sales-channel Minor
@medusajs/search Minor
@medusajs/settings Minor
@medusajs/stock-location Minor
@medusajs/store Minor
@medusajs/tax Minor
@medusajs/translation Minor
@medusajs/user Minor
@medusajs/workflow-engine-inmemory Minor
@medusajs/workflow-engine-redis Minor
@medusajs/search-postgres Minor
@medusajs/oas-github-ci Minor
@medusajs/cache-inmemory Minor
@medusajs/cache-redis Minor
@medusajs/event-bus-local Minor
@medusajs/analytics-local Minor
@medusajs/analytics-posthog Minor
@medusajs/auth-emailpass Minor
@medusajs/auth-github Minor
@medusajs/auth-google Minor
@medusajs/auth-oidc Minor
@medusajs/caching-redis Minor
@medusajs/file-local Minor
@medusajs/file-s3 Minor
@medusajs/fulfillment-manual Minor
@medusajs/locking-postgres Minor
@medusajs/locking-redis Minor
@medusajs/notification-local Minor
@medusajs/notification-sendgrid Minor
@medusajs/payment-stripe Minor
@medusajs/instantsearch-adapter Minor
create-medusa-app Minor
@medusajs/http-types-generator Minor
@medusajs/deps Minor
@medusajs/eslint-plugin Minor
@medusajs/telemetry Minor
@medusajs/admin-sdk Minor
@medusajs/admin-shared Minor
@medusajs/admin-vite-plugin Minor
@medusajs/icons Minor
@medusajs/toolbox Minor
@medusajs/ui-preset Minor
@medusajs/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@medusa-os-bot

medusa-os-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

Thanks for the contribution! A few items need to be addressed before this can move forward:

The contributor (pmwheatley) adds weight-based shipping support to Medusa: a new nullable unit_weight column on cart_line_item, a weight_total computed total on line items and carts, weight_total accepted as a shipping-option price-rule attribute in the admin validator, corresponding admin dashboard UI for weight-based conditional prices (with range-overlap validation and full i18n coverage), and documentation updates. The implementation logic is correct: weight flows from product-variant data through prepareLineItemData, is rounded to an integer before persistence, and is aggregated by the existing BigNumber-safe totals utilities. No security issues, performance regressions, or bugs were found. Two required changes are listed below.

  • Large contribution without a pre-approved issue: the PR changes 67 files and over 14,000 lines across the core cart module, totals utilities, admin API validator, admin dashboard, and docs — well above the 500-line / 20-file threshold. Per CONTRIBUTING.md, contributions of this scope should be scoped and pre-approved via a GitHub issue before a PR is opened. Please open an issue describing the feature (or link an existing one) and get team sign-off before resubmitting.
  • Changeset bump type: .changeset/tangy-pandas-weigh.md sets @medusajs/medusa and @medusajs/cart to minor. Per the project's contribution conventions (CLAUDE.md), minor is reserved for breaking changes (removed exports, changed signatures, altered existing behaviour). Adding new optional fields (unit_weight, weight_total) and extending the price-rule allowlist are non-breaking additions, so both packages should use patch instead.

Triggered by: new PR opened

@medusa-os-bot

medusa-os-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

Thanks for the contribution! A few items need to be addressed before this can move forward:

The contributor adds weight-based shipping support: a new nullable unit_weight column on cart_line_item, a server-side computed weight_total total (sum of each line item's unit_weight × quantity), an extension of the shipping-option price-rule Zod validator to accept weight_total as a valid attribute, corresponding admin dashboard UI with i18n coverage across all supported locales, and documentation updates. The implementation approach is sound — weights flow from product/variant data through prepareLineItemData, are rounded to integers on persistence in the cart module service, and are aggregated with the existing BigNumber-safe math utilities. No security issues, N+1 queries, or correctness bugs were found in this review. Two required changes from the previous review remain unresolved.

  • Large contribution without a pre-approved issue: the PR changes 67 files and over 14,000 lines across the cart module, totals utilities, admin API validator, admin dashboard, and docs — well above the 500-line / 20-file threshold. Per CONTRIBUTING.md, contributions of this scope require an issue with team buy-in before a PR is opened. Please open or link an existing issue, get team sign-off, and resubmit.
  • Changeset bump type: .changeset/tangy-pandas-weigh.md marks @medusajs/medusa and @medusajs/cart as minor. Per the project's changeset convention, minor is reserved for breaking changes (removed exports, changed signatures, altered behaviour); non-breaking additions use patch. The new unit_weight column, weight_total computed total, and extended price-rule allowlist are all additive and backward-compatible — change both entries to patch.

Triggered by: PR description updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support weight-based shipping prices via weight_total totals and conditional price rules

1 participant