Skip to content

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

Description

@pmwheatley

Problem

Merchants who ship physical goods almost always pay carriers by weight, but Medusa gives them no way to price by weight.

A merchant selling board games, books, or apparel typically has carrier rate cards that look like "up to 500g: €5.50, 500g–2kg: €8.00, 2kg–5kg: €11.00". Today, the only conditional price attribute for shipping options is item_total. Weight correlates with price, but only loosely: a €90 plush toy might weigh 200g while a €15 game weighs 3kg. Basing shipping prices on order value means either overcharging light, expensive orders or losing money on heavy, cheap ones.

From the merchant's day-to-day perspective this shows up as:

  • Rate tables can't be expressed. The natural model (a flat option with weight-banded conditional prices) doesn't exist, so teams either flatten their rate card into a single average price or maintain awkward splits.
  • Fragile workarounds. Custom ShippingOptionRules can restrict whether an option is available by weight, but they can't vary the price. The remaining escape hatches (calculated providers, per-variant shipping hacks, external pricing services) add operational complexity far beyond what a rate-card-driven business needs.
  • Manual price babysitting. During checkout review, staff eyeball heavy orders and eat the difference or issue manual refunds/discounts on shipping — a recurring, error-prone cost leak.
  • Migration pain. Stores moving from platforms with weight-based shipping plugins (a common WooCommerce setup) find their entire rate structure unrepresentable and must redesign pricing as part of the migration.

What merchants expect, as users of the admin and the pricing model, is to open a shipping option, add conditional prices of the form "if cart weight is between X and Y, charge Z" — exactly the pattern item_total already supports for order value — and have storefront checkout resolve the right price automatically from the weights already stored on their product variants.

Proposal

Add weight as a first-class conditional dimension for flat-rate shipping options:

  1. Cart line item weight: persist a unit_weight on cart_line_item, inherited from the product variant's weight (falling back to the product's weight).
  2. Cart weight total: compute weight_total on line item totals (unit_weight × quantity) and cart totals (Σ line items), exposed on CartLineItemDTO / CartDTO alongside the other BigNumber totals.
  3. Price rule attribute: whitelist weight_total in the shipping option price-rule attribute enum (alongside item_total), so calculated-price selection at checkout matches on cart weight.
  4. Admin dashboard: offer weight_total in the shipping option conditional price form, with range-overlap validation for weight bands.

Example

await sdk.admin.shippingOption.create({
  name: "Standard Shipping",
  service_zone_id: "serzo_123",
  shipping_profile_id: "sp_123",
  provider_id: "manual",
  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 }],
    },
  ],
})

weight_total rules combine with item_total rules (and any other attributes) using AND semantics.

Notes

  • Carts whose items have no weight set compute weight_total as 0 — they match lte-style bands starting at zero, so a sensible default/fallback price still applies.
  • Price rules affect flat-rate options only; calculated options are priced by the provider (unchanged).
  • Fully backwards compatible: new column is nullable, totals default to zero, no behavior change for existing price rules.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions