Create coupons in Stripe via the API with stripe.coupons.create() specifying percent_off or amount_off, duration (once, repeating, or forever), and optional limits. Then create promotion codes with stripe.promotionCodes.create() to give customers a shareable code like SAVE20. Apply coupons to Checkout Sessions, subscriptions, or invoices.
Coupons and Promotion Codes in Stripe
Stripe uses a two-layer discount system. A Coupon defines the discount logic (20% off, $10 off, etc.) and duration. A Promotion Code wraps a coupon in a customer-facing code string (like SAVE20) that customers enter at checkout. You can create coupons without promotion codes for internal use (applied directly via API), or create promotion codes for self-service discounts. Both work with Checkout Sessions, subscriptions, and invoices.
Prerequisites
- A Stripe account with API keys from Dashboard → Developers → API keys
- Node.js 18 or newer installed
- The stripe npm package installed (npm install stripe)
- A product or Checkout Session to apply the coupon to
Step-by-step guide
Create a percentage coupon
Create a percentage coupon
Create a coupon that gives a percentage discount. Set percent_off and duration. Duration options: 'once' (single use), 'repeating' (N months), or 'forever' (applies indefinitely for subscriptions).
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23const coupon = await stripe.coupons.create({4 percent_off: 20,5 duration: 'once',6 name: '20% Off First Purchase',7 max_redemptions: 100, // optional: limit total uses8 redeem_by: Math.floor(new Date('2026-12-31').getTime() / 1000), // optional: expiry9});1011console.log('Coupon ID:', coupon.id);Expected result: A coupon is created that gives 20% off for one payment, limited to 100 uses.
Create a fixed-amount coupon
Create a fixed-amount coupon
Use amount_off instead of percent_off for a fixed discount. You must also specify the currency.
1const coupon = await stripe.coupons.create({2 amount_off: 1000, // $10.00 in cents3 currency: 'usd',4 duration: 'repeating',5 duration_in_months: 3, // applies for 3 months of a subscription6 name: '$10 Off for 3 Months',7});Expected result: A coupon is created that gives $10 off each month for 3 months.
Create a promotion code
Create a promotion code
Wrap the coupon in a promotion code that customers can enter. The code string is what customers type at checkout.
1const promoCode = await stripe.promotionCodes.create({2 coupon: coupon.id,3 code: 'SAVE20',4 max_redemptions: 50,5 restrictions: {6 first_time_transaction: true, // only for new customers7 minimum_amount: 5000, // minimum $50.00 order8 minimum_amount_currency: 'usd',9 },10});1112console.log('Promo code:', promoCode.code);Expected result: A promotion code SAVE20 is created that applies the 20% off coupon with restrictions.
Apply a coupon to a Checkout Session
Apply a coupon to a Checkout Session
Enable promotion codes in your Checkout Session so customers can enter the code, or apply a coupon directly without requiring customer input.
1// Option 1: Let customers enter promo codes at checkout2const session = await stripe.checkout.sessions.create({3 mode: 'payment',4 allow_promotion_codes: true, // shows promo code input field5 line_items: [{6 price_data: {7 currency: 'usd',8 product_data: { name: 'Annual Plan' },9 unit_amount: 9900, // $99.0010 },11 quantity: 1,12 }],13 success_url: 'https://yoursite.com/success',14 cancel_url: 'https://yoursite.com/cancel',15});1617// Option 2: Apply coupon directly (no code needed from customer)18const session2 = await stripe.checkout.sessions.create({19 mode: 'payment',20 discounts: [{ coupon: coupon.id }],21 line_items: [{ /* ... */ }],22 success_url: 'https://yoursite.com/success',23 cancel_url: 'https://yoursite.com/cancel',24});Expected result: Option 1: Checkout page shows a promo code input field. Option 2: Discount is pre-applied.
Complete working example
1const express = require('express');2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);34const app = express();5app.use(express.json());67// Create a coupon8app.post('/api/coupons', async (req, res) => {9 try {10 const { percent_off, amount_off, currency, duration, duration_in_months, name, max_redemptions } = req.body;1112 const params = { duration, name };13 if (percent_off) params.percent_off = percent_off;14 if (amount_off) {15 params.amount_off = amount_off;16 params.currency = currency || 'usd';17 }18 if (duration === 'repeating') params.duration_in_months = duration_in_months;19 if (max_redemptions) params.max_redemptions = max_redemptions;2021 const coupon = await stripe.coupons.create(params);22 res.json({ id: coupon.id, name: coupon.name });23 } catch (err) {24 res.status(500).json({ error: err.message });25 }26});2728// Create a promotion code for a coupon29app.post('/api/promo-codes', async (req, res) => {30 try {31 const { couponId, code, max_redemptions, first_time_only } = req.body;3233 const params = { coupon: couponId };34 if (code) params.code = code;35 if (max_redemptions) params.max_redemptions = max_redemptions;36 if (first_time_only) {37 params.restrictions = { first_time_transaction: true };38 }3940 const promoCode = await stripe.promotionCodes.create(params);41 res.json({ id: promoCode.id, code: promoCode.code });42 } catch (err) {43 res.status(500).json({ error: err.message });44 }45});4647// Create checkout with promo code support48app.post('/api/checkout', async (req, res) => {49 try {50 const session = await stripe.checkout.sessions.create({51 mode: 'payment',52 allow_promotion_codes: true,53 line_items: [{54 price_data: {55 currency: 'usd',56 product_data: { name: 'Pro Plan' },57 unit_amount: 4900,58 },59 quantity: 1,60 }],61 success_url: `${req.headers.origin}/success`,62 cancel_url: `${req.headers.origin}/cancel`,63 });6465 res.json({ url: session.url });66 } catch (err) {67 res.status(500).json({ error: err.message });68 }69});7071const PORT = process.env.PORT || 3000;72app.listen(PORT, () => console.log(`Server on port ${PORT}`));Common mistakes when creating coupon codes in Stripe
Why it's a problem: Confusing coupons with promotion codes
How to avoid: A coupon is the discount logic (20% off). A promotion code is a customer-facing code string (SAVE20) linked to a coupon. Create the coupon first, then optionally create promotion codes for it.
Why it's a problem: Using both allow_promotion_codes and discounts in the same Checkout Session
How to avoid: These are mutually exclusive. Use allow_promotion_codes to let customers enter codes, OR use discounts to pre-apply a coupon. You cannot use both.
Why it's a problem: Setting amount_off without specifying currency
How to avoid: Fixed-amount coupons require a currency field. Without it, the API returns an error.
Why it's a problem: Forgetting to set max_redemptions or redeem_by limits
How to avoid: Without limits, coupons can be used unlimited times forever. Always set max_redemptions and/or redeem_by for marketing campaigns.
Best practices
- Create coupons with descriptive names (e.g., '20% Off First Purchase Q1 2026') for easy Dashboard identification
- Set max_redemptions and redeem_by dates on coupons to prevent unlimited usage
- Use promotion codes for customer-facing discounts and direct coupon application for internal/automatic discounts
- Test coupons with card 4242424242424242 in test mode to verify the discount is applied correctly
- Use first_time_transaction restriction on promotion codes to limit discounts to new customers
- Track coupon performance in Dashboard → Coupons to see redemption counts and revenue impact
- Delete or deactivate expired coupons to keep your catalog clean
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a Node.js Express API for Stripe coupon management. Include endpoints to create percentage and fixed-amount coupons, create promotion codes with restrictions, and create Checkout Sessions with allow_promotion_codes enabled. Use the stripe npm package.
Add a discount system to my app using Stripe. Create coupons with percentage or fixed discounts, generate shareable promo codes with first-time customer restrictions, and integrate them into Checkout Sessions. Include an admin endpoint to list all active coupons.
Frequently asked questions
What is the difference between a coupon and a promotion code?
A coupon defines the discount rules (percentage, amount, duration). A promotion code is a shareable string (like SAVE20) linked to a coupon that customers can enter at checkout. You need a coupon first, then optionally create promotion codes.
Can I apply multiple coupons to one payment?
No. Stripe allows only one coupon per Checkout Session, subscription, or invoice. For stacking discounts, create a single coupon that represents the combined discount.
Can I edit a coupon after creating it?
You can update the name and metadata of a coupon, but not the discount amount, duration, or other terms. To change the discount, create a new coupon.
How do coupons work with subscriptions?
For subscriptions, 'once' applies the discount to the first invoice only, 'repeating' applies for a set number of months, and 'forever' applies to every invoice for the life of the subscription.
Can customers use a promotion code more than once?
By default, each customer can use a promotion code once. Set max_redemptions on the promotion code to limit total uses across all customers.
What if I need a complex discount system with tiered pricing and referral codes?
For advanced discount logic like volume-based pricing, referral programs, and multi-tier coupon systems, the RapidDev team can help design and implement a custom solution on top of Stripe's coupon API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation