Skip to main content
In this tutorial, you’ll learn how to implement seat-based pricing using Dodo Payments add-ons. We’ll create a subscription product with add-ons for additional seats and show you how to generate payment links with custom add-on quantities.
This tutorial provides sample implementation code for a Node.js/Express application. You can modify this code for your specific framework (Next.js, React, Vue, etc.) and customize the user interface according to your application’s needs.
By the end of this tutorial, you’ll know how to:
  • Create subscription products with seat-based pricing
  • Set up add-ons for additional seats
  • Generate payment links with custom add-on quantities
  • Handle checkout sessions with dynamic seat counts

What We’re Building

Let’s create a seat-based pricing model:
  • Base Plan: $49/month for up to 5 team members
  • Seat Add-on: $2/month per additional seat
  • Payment Links: Dynamic checkout with custom seat quantities
Before we start, make sure you have:
  • A Dodo Payments account
  • Basic familiarity with TypeScript/Node.js

Step 1: Create Your Seat Add-On

Now we need to create an add-on that represents additional seats. This add-on will be attached to our base subscription and allow customers to purchase additional seats.
Creating base subscription product
What we’re building: An add-on that costs $2/month per seat and can be added to any base subscription.
1

Navigate to Add-Ons

  1. In your Dodo Payments dashboard, stay in the Products section
  2. Click on the Add-Ons tab
  3. Click Create Add-On
This will open the add-on creation form.
2

Enter add-on details

Fill in these values for our seat add-on:Add-On Name: Additional Team SeatDescription: Add extra team members to your workspace with full access to all featuresPrice: Enter → 2.00Currency: Must match your base subscription currencyTax Category: Select appropriate category for your product.
3

Save your add-on

  1. Review all your settings:
    • Name: Additional Team Seat
    • Price: $2.00/month
  2. Click Create Add-On
Add-on created! Your seat add-on is now available to attach to subscriptions.

Step 2: Create Your Base Subscription Product

We’ll start by creating a base subscription product that includes 5 team members. This will be the foundation of our seat-based pricing model.
Creating base subscription product
1

Navigate to Products

  1. Log into your Dodo Payments dashboard
  2. Click on Products in the left sidebar
  3. Click the Create Product button
  4. Select Subscription as the product type
You should see a form where we’ll configure our base subscription.
2

Fill in the subscription details

Now we’ll enter the specific details for our base plan:Product Name: MotionDescription: Where your team's documentation lives.Recurring Price: Enter → 49.00Billing Cycle: Select → MonthlyCurrency: Select your preferred currency (e.g., USD)

Step 3: Connect Add-On to Subscription

Now we need to associate our seat add-on with the base subscription so customers can purchase additional seats during checkout.
1

Attach the seat add-on

Attaching add-on to subscription
  1. Scroll down to the Add-Ons section
  2. Click Add Add-Ons
  3. From the dropdown, select your seat add-on
  4. Confirm that it appears in your subscription configuration
2

Save subscription changes

  1. Review your complete subscription setup:
    • Base plan: $49/month for 5 seats
    • Add-on: $2/month per additional seat
    • Free trial: 14 days
  2. Click Save Changes
Seat-based pricing configured! Customers can now purchase your base plan and add extra seats as needed.
Now let’s create an Express.js application that generates payment links with custom add-on quantities. This is where the real power of seat-based pricing comes in - you can dynamically create checkout sessions with any number of additional seats.
1

Set up your project

Create a new Node.js project and install the required dependencies:
mkdir seat-based-pricing
cd seat-based-pricing
npm init -y
npm install dodopayments express dotenv
npm install -D @types/node @types/express typescript ts-node
Create a tsconfig.json file:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
2

Create your environment file

Create a .env file with your Dodo Payments API key:
DODO_PAYMENTS_API_KEY=your_actual_dodo_api_key_here
Never commit your API key to version control. Add .env to your .gitignore file.
3

Implement the checkout session creation

Create a src/server.ts file with the following code:
// Add this new endpoint for dynamic seat quantities
import 'dotenv/config';
import DodoPayments from 'dodopayments';
import express, { Request, Response } from 'express';

const app = express();

// Initialize the Dodo Payments client
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode'
});

async function createCheckoutSession(seatCount: number) {
  try {
    const session = await client.checkoutSessions.create({
      // Products to sell - use IDs from your Dodo Payments dashboard
      product_cart: [
        {
          product_id: 'pdt_7Rl9OWT2Mz4wwUTKz74iZ', // Replace with your actual product ID
          quantity: 1,
          addons: [
            {
              addon_id: 'adn_eKQbNakKrivDpaxmI8wKI', // Replace with your actual addon ID
              quantity: seatCount
            }
          ]
        }
      ],
      
      // Pre-fill customer information to reduce friction
      customer: {
        email: 'steve@example.com',
        name: 'Steve Irwin',
      },
      // Where to redirect after successful payment
      return_url: 'https://example.com/checkout/success',
    });

    // Redirect your customer to this URL to complete payment
    console.log('Checkout URL:', session.checkout_url);
    console.log('Session ID:', session.session_id);
    
    return session;
    
  } catch (error) {
    console.error('Failed to create checkout session:', error);
    throw error;
  }
}

// Example usage in an Express.js route
app.post('/create-checkout/:seatCount', async (req: Request, res: Response) => {
  try {
    const seatCount = parseInt(req.params.seatCount);
    const session = await createCheckoutSession(seatCount);
    res.json({ checkout_url: session.checkout_url });
  } catch (error) {
    res.status(500).json({ error: 'Failed to create checkout session' });
  }
});

// Add this line after your other middleware
app.use(express.static('public'));

// Add this route to serve the demo page
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/../public/index.html');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
4

Add a simple web interface

Create a public/index.html file for easy testing:
<!DOCTYPE html>
<html>
<head>
    <title>Seat-Based Pricing Demo</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
        .form-group { margin: 20px 0; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
        button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
        button:hover { background: #0056b3; }
        .result { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; }
    </style>
</head>
<body>
    <h1>Seat-Based Pricing Demo</h1>
    <p>Generate checkout links with custom seat quantities:</p>
    
    <div class="form-group">
        <label for="seatCount">Number of Additional Seats:</label>
        <input type="number" id="seatCount" value="3" min="0" max="50">
    </div>
    
    <button onclick="createCheckout()">Generate Checkout Link</button>
    
    <div id="result" class="result" style="display: none;">
        <h3>Checkout Link Generated!</h3>
        <p><strong>Seat Count:</strong> <span id="seatCountDisplay"></span></p>
        <p><strong>Total Cost:</strong> $<span id="totalCost"></span>/month</p>
        <p><strong>Checkout URL:</strong></p>
        <a id="checkoutUrl" href="#" target="_blank">Click here to checkout</a>
    </div>

    <script>
        async function createCheckout() {
            const seatCount = document.getElementById('seatCount').value;
            
            try {
                const response = await fetch(`/create-checkout/${seatCount}`, {
                    method: 'POST'
                });
                
                const data = await response.json();
                
                if (response.ok) {
                    document.getElementById('seatCountDisplay').textContent = seatCount;
                    document.getElementById('totalCost').textContent = data.total_cost;
                    document.getElementById('checkoutUrl').href = data.checkout_url;
                    document.getElementById('result').style.display = 'block';
                } else {
                    alert('Error: ' + data.error);
                }
            } catch (error) {
                alert('Error creating checkout session');
            }
        }
    </script>
</body>
</html>
Web interface created! You now have a simple UI to test different seat quantities.
5

Serve static files

Add this to your src/server.ts to serve the HTML file:
// Add this line after your other middleware
app.use(express.static('public'));

// Add this route to serve the demo page
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/../public/index.html');
});
Static files configured! Visit http://localhost:3000 to see your demo interface.

Step 5: Test Your Implementation

Let’s test our seat-based pricing implementation to make sure everything works correctly.
1

Start your server

  1. Make sure you have your .env file with the correct API key
  2. Update the product and add-on IDs in your code with the actual values from your Dodo Payments dashboard
  3. Start your server:
npm run dev
Your server should start successfully and show “Server running on http://localhost:3000
2

Test the web interface

Creating base subscription product
  1. Open your browser and go to http://localhost:3000
  2. You should see the seat-based pricing demo interface
  3. Try different seat quantities (0, 3, 10, etc.)
  4. Click “Generate Checkout Link” for each quantity
  5. Verify that the checkout URLs are generated correctly
3

Test a checkout session

  1. Generate a checkout link with 3 additional seats
  2. Click the checkout URL to open the Dodo Payments checkout
  3. Verify that the checkout shows:
    • Base plan: $49/month
    • Additional seats: 3 × 2 dollars = $6/month
  4. Complete the test purchase
The checkout should display the correct pricing breakdown and allow you to complete the purchase.
4

Listen for webhooks and update your DB

To keep your database in sync with subscription and seat changes, you need to listen for webhook events from Dodo Payments. Webhooks notify your backend when a customer completes checkout, updates their subscription, or changes seat counts.Follow the official Dodo Payments webhooks guide for step-by-step instructions on setting up webhook endpoints and handling events:

Dodo Payments Webhooks Documentation

Learn how to securely receive and process webhook events for subscription and seat management.

Troubleshooting

Common issues and their solutions:
Possible causes:
  • Invalid product ID or add-on ID
  • API key doesn’t have sufficient permissions
  • Add-on not properly associated with subscription
  • Network connectivity issues
Solutions:
  1. Verify product and add-on IDs exist in your Dodo Payments dashboard
  2. Check that add-on is properly attached to the subscription
  3. Ensure API key has checkout session creation permissions
  4. Test API connectivity with a simple GET request

Congratulations! You’ve Implemented Seat-Based Pricing

You’ve successfully created a seat-based pricing system with Dodo Payments! Here’s what you accomplished:

Base Subscription

Created a subscription product with 5 included seats at $49/month

Seat Add-ons

Configured add-ons for additional seats at $2/month per seat

Checkout

Built an API that generates checkout sessions with custom seat quantities

Web Interface

Created a simple web interface for testing different seat quantities
This example demonstrates only a minimal implementation of seat-based pricing. For production use, you should add robust error handling, authentication, data validation, security measures, and adapt the logic to fit your application’s requirements.