MarketingDiscount rulesRecipes

Recipes

Ready-made discount rules you can copy, covering free shipping, BOGO, volume tiers, "3 for 499" set pricing, bundles, Happy Hour, VIP pricing, members-only, and geo-targeted shipping.

Proven automatic-discount rules you can copy. Pick the one closest to your goal, open Discount rules → + Add Rule, enter the Type, Filters, Conditions, and Discount config listed, and it goes live on every cart within seconds. New to rules? Read the Discount rules walkthrough first. For code-based campaigns, see Coupon recipes.

Free shipping over $100

  • Type: Shipping Discount
  • Conditions: Cart Subtotal >= 100
  • Filters: none. Leave the block empty so the rule covers every order that meets the condition. Filters do work on a Shipping Discount: adding one restricts free delivery to carts holding a matching item, which is not what this recipe wants. Use a Subtotal by Category / Subtotal by Products condition instead when the gate is an amount spent rather than an item being present.
  • Discount config: { "method": "free" }
  • Display: Banner "Free shipping on orders over $100!" + Cart nudge "Add ${AMOUNT_NEEDED} more for free shipping"

Keep it on "method": "free" if you want the saving visible in the cart. A partial version ({ "method": "percentage", "value": 50 } or { "method": "fixed_amount", "value": 10 }) shows as $0.00 in the cart and only comes off once the shopper picks a delivery method.

Buy 2 AirPods, get 1 free (same product)

  • Type: Buy X Get X
  • Filters: Products [ AirPods Pro 2 ]
  • Discount config: { "buyQuantity": 2, "getQuantity": 1, "getDiscountPercent": 100 }
  • Display: Badge "Buy 2 Get 1 Free"

Buy 2 shirts, get a free tie (different product)

  • Type: Buy X Get Y
  • Filters: Category [ Shirts ]
  • Discount config:
    {
      "buyQuantity": 2,
      "getQuantity": 1,
      "getDiscountPercent": 100,
      "getFilters": { "categories": ["cat_ties"] }
    }
  • Display: Badge "Free tie with 2 shirts"

"cat_ties" stands for a real category ID, not the name "Ties" and not a slug. Fetch it with listCategories (the same goes for products, tags and brands, via getProducts, listTags, listBrands). A name is rejected with a 400. skus and attributes are the exception: free text, never checked, so a typo saves fine and then matches nothing. Building this in the dashboard instead? The pickers always send the right ids, so this only affects API calls.

  • Type: Volume Discount
  • Filters: Products or SKUs, meaning the specific cables and adapters you want at $3.99, not a whole category
  • Discount config: 3 tiers, 3+ → 10% off, 5+ → 15% off, 10+ → $3.99 each (absolute)
  • Display: Cart nudge "Add 1 more accessory to unlock 10% off!"
  • Max budget: $5,000 (caps the total cost)

Volume: "3 for $499" on one product

  • Type: Volume Discount
  • Filters: Products, the one product the offer is for
  • Discount config: one tier, quantity 3, method Set price, value 499
  • Conditions: none. The set size already says "three of this product", and a cart-wide quantity condition would count everything else in the cart too.

Three units cost $499 together. The set repeats, so six cost $998, and anything past the last full set stays at full price, so four units cost $499 plus one at full price.

Pick Set price, not Price per unit. They sit next to each other and take the same two numbers, but Price per unit reads $499 as the price of a single item. On a $199 product that is above its own price, so nothing is discounted, and the rule saves and shows as active while never firing. The line under the tier row spells out which one you have: "every 3 units cost $499 together" versus "3 units = $1,497".

Volume: bulk pricing with a per-unit floor

Why the narrow filter matters. The 10+ threshold counts every filtered unit together (4 cables plus 6 adapters unlocks it) and once it unlocks, every filtered unit is re-priced to $3.99. Point this at a whole "Charging Accessories" category and a $200 charging dock sitting in that category drops to $3.99 with the cables. A percentage tier is safe on a broad category because it scales with each item's own price; a fixed_price tier is not. (Units already cheaper than the tier price keep their own price, because a tier never raises what someone pays.)

$50 off orders over $500, first-time customers only

  • Type: Order Discount
  • Conditions: First Order = Yes AND Cart Subtotal >= 500
  • Discount config: { "method": "fixed_amount", "value": 50 }
  • Per customer: 1 (one-shot deal)

MacBook + AirPods bundle

  • Type: Bundle
  • Discount config: bundlePrice: 1299, components = [{products: [MacBook Air], qty: 1}, {products: [AirPods Pro], qty: 1}], maxBundlesPerCart: 1
  • Display: Badge "Bundle, save 15%"

Building this through the API? Component products / categories / tags / brands take IDs, not product names. getProducts gives you them, and a name is rejected with a 400. If you use skus in a component instead, note that SKUs are free text and are never checked: a typo means that slot can never be filled, so the bundle simply never forms and no one is told why.

Happy Hour: Thursday 17:00 to 19:00

  • Type: Product Discount (or Order Discount)
  • Conditions:
    • Day of Week in [ THU ]
    • Hour Range between 5:00pm and 7:00pm in the time picker. (Setting this through the API? The values are minutes from midnight: 1020 and 1140, not 17 and 19, which would give you a two-minute window just after midnight.)
  • Discount config: { "method": "percentage", "value": 20 }
  • Display: Banner "Happy Hour! 20% off until 7 PM"
  • Timezone reminder: Set Settings → Timezone to your local IANA zone first!

VIP tier: 15% off for high-spenders

  • Type: Product Discount
  • Conditions: Total Spent >= 5000
  • Discount config: { "method": "percentage", "value": 15 }
  • Display: Badge "VIP Pricing"
  • Stacking: Exclusive ON and Priority 0, because Exclusive only stops the rules that come after this one in priority order, so without the lowest priority number in your store a cheaper rule runs first and VIP pricing stacks on top of it.

VIP conditions need a signed-in customer: Total Spent is false for a guest, so this rule never fires for someone shopping logged out, however much they have spent in the past.

Members-only bundle

  • Type: Bundle
  • Conditions: Is Logged In = Yes
  • Filters/components: as the bundle recipe above
  • Display: Badge "Members Only"

Geo-targeted free shipping (IL + US only)

  • Type: Shipping Discount
  • Conditions:
    • Shipping Country in [ IL, US ]
    • Cart Subtotal >= 75
  • Discount config: { "method": "free" }
  • Display: Banner "Free shipping to Israel & US over $75", but read the next paragraph before you write that banner.

A shipping-country condition cannot pass until the shopper has typed an address. While they browse and while they are in the cart, there is no address yet, so this rule stays quiet: no discount line, no cart nudge, nothing. It starts applying from the address step of checkout onward. A banner promising free shipping is therefore visible on pages where the cart shows full delivery cost, which reads as a broken promise. Either word it as a condition ("Free shipping to Israel & the US on orders over $75, applied at checkout"), or drop the banner and let it appear at the delivery step.

What's next?