> For the complete documentation index, see [llms.txt](https://developer.paddle.com/llms.txt).

# Build inline checkout

Get a step-by-step overview of how to build a complete inline checkout — including initializing Paddle.js, passing settings and items, updating on-page information, and next steps.

---

The checkout is where customers make purchases. For SaaS businesses, it's the process where customers enter their details and payment information, and confirm that they'd like to sign up for a subscription with you.

You can use [Paddle.js](https://developer.paddle.com/paddlejs/overview.md) to integrate an inline checkout into your app. [Inline checkout](https://developer.paddle.com/concepts/sell/branded-integrated-inline-checkout.md) lets you embed a checkout and display information about items and totals in your own interface, creating checkout experiences that are fully integrated with your app.

[Explore the code for this tutorial and test right away using our inline checkout pen.](https://codepen.io/heymcgovern/pen/yLrogZd)

## What are we building?

In this tutorial, we'll build a page that embeds a multi-page inline checkout for three items in our product catalog. We'll display information about items and totals in a table on the page, add a way for customers to switch to annual plan during checkout, then we'll extend it by passing customer information.

We'll learn how to:

- Include and set up Paddle.js using a client-side token
- Pass settings to Paddle.js to embed an inline checkout in our page
- Pass items to inline checkout using `Paddle.Checkout.open()`
- Display and update information about items and totals using checkout events
- Take a test payment
- Update items for an opened checkout using `Paddle.Checkout.updateCheckout()`

If you like, you can [view on CodePen](https://codepen.io/heymcgovern/pen/yLrogZd) and follow along.

## Before you begin

### Choose a checkout implementation

This tutorial walks through creating an [inline checkout](https://developer.paddle.com/concepts/sell/branded-integrated-inline-checkout.md). You can also create [overlay checkouts](https://developer.paddle.com/concepts/sell/overlay-checkout.md), which let you launch a checkout in just a few lines of code.

We recommend [building an overlay checkout](https://developer.paddle.com/build/checkout/build-overlay-checkout.md) if you're new to Paddle. Inline checkouts use the same JavaScript methods as overlay checkouts, so you can switch to an inline checkout later.

{% collapsible title="Types of checkout" %}

{% feature-comparison level=4 %}

{% feature-column title="Overlay checkout" %}

{% feature-item %}
Integrate Paddle in just a few lines of code. Launches an overlay to capture payment.
{% /feature-item %}

{% feature-item %}
Quick to set up: add Paddle.js to your site and call `Paddle.Checkout.open()` or use `data-*` HTML attributes to launch the checkout.
{% /feature-item %}

{% feature-item %}
Checkout opens as a modal overlay on top of your page, focusing the user on payment.
{% /feature-item %}

{% feature-item %}
Customize colors, logo, and branding in the Paddle dashboard. Structure and experience is managed by Paddle.
{% /feature-item %}

{% /feature-column %}

{% feature-column title="Inline checkout" %}

{% feature-item %}
Get complete control of the checkout experience. Captures payment directly in your app.
{% /feature-item %}

{% feature-item %}
Embed Paddle Checkout directly into your page layout. Use a container element and set `displayMode` to `inline`.
{% /feature-item %}

{% feature-item %}
Checkout form is part of your page flow—lets you create fully integrated or branded payment experiences.
{% /feature-item %}

{% feature-item %}
Full control over surrounding layout and styling; you can design the context and surrounding UX.
{% /feature-item %}

{% /feature-column %}

{% /feature-comparison %}

To learn more about the differences between overlay and inline checkouts, see [Paddle Checkout](https://developer.paddle.com/concepts/sell/self-serve-checkout.md)

{% /collapsible %}

### Create products and prices

Paddle Checkout works with products and prices to say what a customer is purchasing, so you'll need to [create a product and at least one related price](https://developer.paddle.com/build/checkout/build-branded-inline-checkout.md) to pass to your checkout.

### Set your default payment link

You'll also need to:

- [Set your default payment link](https://developer.paddle.com/build/transactions/default-payment-link.md) under **Paddle > Checkout > Checkout settings > Default payment link**.
- Get your default payment link domain approved, if you're working with the live environment.

## Overview

Add an inline checkout to your website or app in five steps:

1. [**Include and initialize Paddle.js**](#include-paddle-js)  
   Add Paddle.js to your app or website, so you can securely capture payment information and build subscription billing experiences.
2. [**Embed and pass checkout settings and items**](#embed-pass-settings)  
   Pass settings to determine how your checkout opens and how it works, then pass items to say what your checkout is for.
3. [**Show and update on-page information**](#frontend-items)  
   Inline checkout handles capturing payment securely. Display an items list and totals on your page, and update using event callbacks.
4. [**Take a test payment**](#test-payment)  
   Make sure that your checkout loads successfully, then take a test payment.
5. [**Update your checkout**](#update-checkout) {% badge label="Optional" variant="outline" /%}  
   Dynamically update items and other information for your opened checkout.

## Include and initialize Paddle.js {% step=true %}

[Paddle.js](https://developer.paddle.com/paddlejs/overview.md) is a lightweight JavaScript library that lets you build rich, integrated subscription billing experiences using Paddle. We can use Paddle.js to securely work with products and prices in our Paddle system, as well as opening checkouts and capturing payment information.

### Include Paddle.js script

Start with a blank webpage, or an existing page on your website. Then, [include Paddle.js](https://developer.paddle.com/paddlejs/include-paddlejs.md) by adding this script to the `<head>`:

```html
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
```

### Set environment {% badge label="Optional" /%}

We recommend [signing up for a sandbox account](https://sandbox-login.paddle.com/signup?utm_source=dx&utm_medium=dev-docs) to test and build your integration, then switching to a live account later when you're ready to go live.

If you're testing with the [sandbox](https://developer.paddle.com/sdks/sandbox.md), call [`Paddle.Environment.set()`](https://developer.paddle.com/paddlejs/methods/paddle-environment-set.md) and set your environment to `sandbox`:

```html {% highlightLines="3" %}
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
<script type="text/javascript">
  Paddle.Environment.set("sandbox");
</script>
```

### Pass a client-side token

Next, go to **Paddle > Developer tools > Authentication** and create a client-side token. [Client-side tokens](https://developer.paddle.com/paddlejs/client-side-tokens.md) let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish.

In your page, call [`Paddle.Initialize()`](https://developer.paddle.com/paddlejs/methods/paddle-initialize.md) and pass your client-side token as `token`. For best performance, do this just after calling `Paddle.Environment.set()`, like this:

```html {% highlightLines="3-5" %}
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
<script type="text/javascript">
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({ 
    token: "test_7d279f61a3499fed520f7cd8c08" // replace with a client-side token
  });
</script>
```

## Embed and pass checkout settings {% step=true %}

Next, we'll set an element on our page as a container for Paddle Checkout and set up Paddle.js for inline checkout.

Inline checkout works by embedding a frame that contains Paddle Checkout into your website or app. The Paddle Checkout frame handles securely capturing payment information, letting you display information about items and totals elsewhere on the page.

### Create checkout container

Create an empty `<div>` for the Paddle Checkout frame and give it a unique `class`, for example `checkout-container`:

```html
<div class="checkout-container"></div>
```

### Pass settings

Now, we'll pass the class of this empty `<div>` to tell Paddle.js where to embed the checkout frame. We'll also [pass checkout settings](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings.md) to tell Paddle.js to load an inline checkout and say how our inline checkout should work.

Update your [`Paddle.Initialize()`](https://developer.paddle.com/paddlejs/methods/paddle-initialize.md) method call so that it includes `checkout.settings`. These settings are applied to all checkouts opened on this page.

In our sample, we pass these settings to `checkout.settings`:

| Parameter                     | Description                                                                            |                                                    |
|----------------------|-----------------------------------------------------------------------------|----------------------------------------------------|
| `displayMode`        | Determines whether Paddle.js should open an inline or overlay checkout.     | We set to `inline`.                                |
| `frameTarget`        | Sets the element where Paddle Checkout should be loaded.                    | We passed our `checkout-container` class name.     |
| `frameInitialHeight` | Sets the initial height of the dev element where Paddle Checkout is loaded. | We set this to `450`, which is our recommendation. |
| `frameStyle`         | CSS properties to apply to the checkout container.                          | We passed some simple CSS styles here.             |

```html {% highlightLines="5-12" %}
<script type="text/javascript">
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({ 
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    }
  });
</script>
```

{% callout type="note" %}
We've covered the required settings for an inline checkout, but you can also pass `locale`, `theme`, and other settings that control how Paddle Checkout works. For more information, see [Pass checkout settings](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings.md)
{% /callout %}

### Pass items

Checkouts must be for one or more items. If we were to try to open a checkout so far, Paddle.js would throw an error.

To pass items, we can use the [`Paddle.Checkout.open()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-open.md) method.

In our sample, we've created a function called `openCheckout()` to open a checkout. Here's how it works:

1. We create a variable called `monthItemsList` and pass an array of objects, where each object contains a `priceId` and `quantity`. In our case, there are two prices that recur monthly and a single one-time price.
2. We create a function called `openCheckout()` that takes a parameter called `items`.
3. In our `openCheckout()` function, we call [`Paddle.Checkout.open()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-open.md), passing the value of `items` as the items list for the checkout.

{% callout type="info" %}
Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually.
{% /callout %}

```html {% highlightLines="15-36" collapse=true %}
<script type="text/javascript">
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    }
  });
  
  // define items
  let monthItemsList = [
    {
      priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  
  // open checkout
  function openCheckout(items){
    Paddle.Checkout.open({
      items: items
    });
  }
</script>
```

### Set openCheckout() to run on page load

Right now, we've written a function to open a checkout, but we haven't set it to run.

We can add `onLoad` to our `<body>` tag to run our `openCheckout()` function immediately after the page has loaded, passing in our `monthItemsList` variable as a parameter:

```html
<body onLoad="openCheckout(monthItemsList)">
```

### Test your work

Save your page, then open it in your browser. Paddle Checkout should load in place of the checkout container `<div>` element we created earlier.

You'll notice that the Paddle Checkout frame doesn't include any information about what the checkout is for. That's our next step.

## Show and update on-page information {% step=true %}

The inline checkout frame doesn't include a breakdown of items or totals. It's designed to handle capturing customer and payment information, giving you the flexibility to show items and totals in your frontend in a way that fits with our design.

To do this, we can use an event callback function. [Paddle.js emits events throughout the checkout process](https://developer.paddle.com/paddlejs/events/overview.md) when key things happen. An event callback function is some code that we run when a specific event occurs.

For example, when checkout is first loaded, Paddle.js emits a [`checkout.loaded`](https://developer.paddle.com/paddlejs/general/checkout-loaded.md) event that contains information about the items and totals on a checkout. We can build an event callback function to update our items and totals table with data contained in the event.

{% collapsible title="What makes a good inline checkout?" isOpen=false  %}
It's important that customers know who they're buying from, what they're buying, and how much they're paying.

To build an inline checkout that's compliant and optimized for conversion, your implementation must include:

1. If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts.
2. A description of what's being purchased.
3. Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too.
4. The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy.
5. A link to your refund policy, if it differs from the Paddle.com standard refund policy.
{% /collapsible %}

### Add tables to hold items and totals

First, we need to add some HTML for a couple of tables to hold items and totals information. You might use something more visual when building an app or website, but tables work for our tutorial.

{% accordion %}
{% accordion-item title="HTML" %}

Add this to the `<body>` of your page.

In this sample, there are two tables for our items and our totals:

- Our items table has a header row and a body row that's got some zero values in. We'll add a row for each item on our checkout later.
- Our totals table has a header column for the totals we'd like to show to our customer. There's `id`s set on the `<td>` elements that should contain totals. We'll use these IDs to replace the contents of these elements with totals later.

```html {% collapse=true %}
<div class="page-container">
  <div class="grid">
    <div class="checkout-container">
    </div>
    <div>
      <h3>Items</h3>
      <table class="items-table">
        <thead>
        <tr>
          <th>Product name</th>
          <th>Price name</th>
          <th>Quantity</th>
          <th>Total</th>
        </tr>
        </thead>
        <tbody>
        <tr>
          <td></td>
          <td></td>
          <td>0</td>
          <td>00.00</td>
        </tr>
        </tbody>
      </table>
      <h3>Totals</h3>
      <table>
        <tbody>
        <tr>
          <td>One-time charges</td>
          <td id="oneTimeTotal">0.00</td>
        </tr>
        <tr>
          <td>Recurring charges</td>
          <td id="recurringTotal">0.00</td>
        </tr>
        <tr>
          <td>Discount</td>
          <td id="discountTotal">0.00</td>
        </tr>
        <tr>
          <td>Taxes</td>
          <td id="taxTotal">0.00</td>
        </tr>
        <tr>
          <td>Total today</td>
          <td id="totalToday">0.00</td>
        </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
```

{% /accordion-item %}
{% accordion-item title="CSS" %}

Add this to the `<head>` of your page. It applies some styling to the HTML so that the `<div>` elements are arranged in two columns.

For this sample, we also include [`MVP.css`](https://andybrewer.github.io/mvp/), which applies light styling to HTML elements. You don't need to do this if you're updating an existing page in your app or website that already has its own styling.

```html {% collapse=true %}
<style>
  .page-container {
    max-width: 1000px;
    margin: 0 auto 2em auto;
    padding-left: 1em;
    padding-right: 1em;
  }
  .grid {
    display: block;
  }
  .grid > * {
    padding: 1rem;
  }
  @media (min-width: 768px) {
    .grid {
      display: grid;
      grid-auto-rows: 1fr;
      grid-template-columns: 1fr 1fr;
    }
  }
  .items-table {
    font-size: smaller;
  }
</style>
<!-- MVP.css: https://andybrewer.github.io/mvp/ -->
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12.0/mvp.css" media="print" onload="this.media='all'">
```

{% /accordion-item %}
{% /accordion %}

### Update items table

Next, we'll build an event callback function to take data about our items from events and display it in our items table.

In our sample, we created a function called `updateTable()` that takes a parameter called `event`. Then, we pass the event payload to our function as `event`. Here's how it works:

1. First, we exclude events that don't return a `name` field and print the data payload of events to the console. This is useful for us to see which events are emitted and how they look while we're testing.
2. We create a variable called `items` and set this to `event.data.items` in our event payload. We'll use this variable to populate our items table.
3. We call another function as part of this event callback function: `updateItemsTable()`, where we pass`items` as a parameter.
4. We create the `updateItemsTable()` function that we called in our event callback, setting it up to accept a parameter called `items`. It finds and selects our items table body (`.items-table tbody`), clears out any rows, then iterates through each item in the `items` array we passed.
5. When iterating through each item, we call another function called `createTableRow()`. We define this underneath, and it accepts `productName`, `priceName`, `quantity`, and `total` — a parameter for each of the columns in our items table.
6. `createTableRow()` returns an HTML table row element with our product name, price name, quantity, and total. This newly created row is appended to our table body in our `updateItemsTable()` function.
7. We update our [`Paddle.Initialize()`](https://developer.paddle.com/paddlejs/methods/paddle-initialize.md) method, passing `updateTable` as the `eventCallback`. This means this function is run every time an event is emitted by Paddle.js.

```html {% highlightLines="2-33" collapse=true %}
<script type="text/javascript">
  function updateTable(event) {
    if (!event.name) {
      return;
    }
    
    console.log(event);
    
    let items = event.data.items;

    updateItemsTable(items);
  }

  function updateItemsTable(items) {
    const itemsTableBody = document.querySelector('.items-table tbody');
    itemsTableBody.innerHTML = '';

    items.forEach(item => {
      const newRow = createTableRow(item.product.name, item.price_name, item.quantity, item.totals.subtotal);
      itemsTableBody.appendChild(newRow);
    });
  }

  function createTableRow(productName, priceName, quantity, total) {
      const newRow = document.createElement('tr');
      newRow.innerHTML = `
      <td>${productName}</td>
      <td>${priceName}</td>
      <td>${quantity}</td>
      <td>${total.toFixed(2)}</td>
    `;
      return newRow;
  }
    
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    },
    eventCallback: updateTable
  });
  
  // define items
  let monthItemsList = [
    {
      priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  
  // open checkout
  function openCheckout(items){
    Paddle.Checkout.open({
      items: items
    });
  }
</script>
```

### Update totals table

Let's update our event callback function so that it displays totals from our events in our totals table.

In our sample, we updated our `updateTable()` function so that it calls another function, `updateSummaryTable()`, to update our totals table. Here's how it works:

1. We create some additional variables called `totals` and `recurringTotals`, setting these to values in our event payload.
2. We add a call to another function as part of this event callback function: `updateSummaryTable()`, where we pass `totals` and `recurringTotals` as parameters.
3. We create the `updateSummaryTable()` function that we called in our event callback, setting it up to accept parameters called `totals` and `recurringTotals`.
4. `updateSummaryTable()` gets cells in our totals table using the IDs that we gave them earlier, then replaces the contents with values from the `totals` and `recurringTotals` arrays that we passed in as parameters. We calculate the one-time total by subtracting the subtotal of recurring items from the subtotal.

{% callout type="warning" %}
For simplicity, we use the built-in `.toFixed()` JavaScript method to format values to two decimal places in our sample. Paddle [supports over 30 currencies](https://developer.paddle.com/concepts/sell/supported-currencies.md), some of which use a different number of decimal places. Consider using a currency library like [currency.js](https://currency.js.org/) to format currencies correctly.
{% /callout %}

```html {% highlightLines="38-44" collapse=true %}
<script type="text/javascript">
  function updateTable(event) {
    if (!event.name) {
      return;
    }
    
    console.log(event);
    
    let items = event.data.items;
    let totals = event.data.totals;
    let recurringTotals = event.data.recurring_totals;

    updateItemsTable(items);
    updateSummaryTable(totals, recurringTotals);
  }

  function updateItemsTable(items) {
    const itemsTableBody = document.querySelector('.items-table tbody');
    itemsTableBody.innerHTML = '';

    items.forEach(item => {
      const newRow = createTableRow(item.product.name, item.price_name, item.quantity, item.totals.subtotal);
      itemsTableBody.appendChild(newRow);
    });
  }

  function createTableRow(productName, priceName, quantity, total) {
    const newRow = document.createElement('tr');
    newRow.innerHTML = `
      <td>${productName}</td>
      <td>${priceName}</td>
      <td>${quantity}</td>
      <td>${total.toFixed(2)}</td>
    `;
    return newRow;
  }

  function updateSummaryTable(totals, recurringTotals) {
    document.getElementById('oneTimeTotal').textContent = (totals.subtotal - recurringTotals.subtotal).toFixed(2);
    document.getElementById('recurringTotal').textContent = recurringTotals.subtotal.toFixed(2);
    document.getElementById('discountTotal').textContent = totals.discount.toFixed(2);
    document.getElementById('taxTotal').textContent = totals.tax.toFixed(2);
    document.getElementById('totalToday').textContent = totals.total.toFixed(2);
  }
    
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    },
    eventCallback: updateTable
  });
  
  // define items
  let monthItemsList = [
    {
      priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  
  // open checkout
  function openCheckout(items){
    Paddle.Checkout.open({
      items: items
    });
  }
</script>
```

## Take a test payment {% step=true %}

We're now ready to test. Save your page, then open it in your browser. Paddle.js should open an inline checkout for the items that we passed. You should see items and totals in the tables we created.

If you're using a sandbox account, you can take a test payment using [our test card details](https://developer.paddle.com/concepts/payment-methods/credit-debit-card.md):

{% definition-list %}
{% definition term="Email address" %}
An email address you own
{% /definition %}
{% definition term="Country" %}
Any valid country supported by Paddle
{% /definition %}
{% definition term="ZIP code (if required)" %}
Any valid ZIP or postal code
{% /definition %}
{% definition term="Card number" %}
`4242 4242 4242 4242`
{% /definition %}
{% definition term="Name on card" %}
Any name
{% /definition %}
{% definition term="Expiration date" %}
Any valid date in the future.
{% /definition %}
{% definition term="Security code" %}
`100`
{% /definition %}
{% /definition-list %}

{% collapsible title="Checkout not working?" %}

If the checkout doesn't appear, or you get a message saying "Something went wrong," you can open your browser console to see any specific error messages from Paddle.js to help you troubleshoot.

{% callout type="info" %}
Use `⌘ Command` + `⌥ Option` + `J` (Mac) or `Ctrl` + `⇧ Shift` + `J` (Windows) to quickly open your browser console in Google Chrome.
{% /callout %}

#### Common problems

Check that:

- You added a default payment link to your checkout under **Paddle > Checkout > Checkout settings > Default payment link**, and that this matches the domain where you're testing. You can use `localhost` if you're testing locally on sandbox.
- You included Paddle.js correctly. If you're moving from Paddle Classic, the CDN URL has changed.
- Your client-side token is correct and passed to [`Paddle.Initialize()`](https://developer.paddle.com/paddlejs/methods/paddle-initialize.md).
- You set the correct environment.
- The Paddle IDs for price entities that you passed are correct. Sandbox and live systems are separate, so make sure you're passing price IDs for the environment that you're working in.
- Your event callback function doesn't have any problems.

{% /collapsible %}

## Update items on the checkout {% step=true badge="Optional" %}

What if we want to update our checkout now it's opened? For example, we might want to let customers adjust the quantity of items or upsell them addons. Paddle.js includes the [`Paddle.Checkout.updateCheckout()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-updatecheckout.md) method to let us dynamically update items, customer information, and discount on a checkout.

For this tutorial, we'll add a button that we can click to switch to annual plan. When customers click this button, we'll swap monthly items on the checkout for annual plan items.

### Define list of prices

[`Paddle.Checkout.updateCheckout()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-updatecheckout.md) has an `items` parameter. When we pass an items list, Paddle.js replaces the items on the checkout with the new items list we passed.

First, we'll define a new variable called `yearItemsList`. Like `monthItemsList`, we'll pass an array of objects, where each object contains a `priceId` and `quantity`. In this case, there are two prices that recur yearly and a single one-time price.

Keep in mind that the entire items list is replaced — any omitted items are removed from the checkout entirely.

{% callout type="info" %}
Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually.
{% /callout %}

```html {% highlightLines="75-88" collapse=true %}
<script type="text/javascript">
  function updateTable(event) {
    if (!event.name) {
      return;
    }
    
    console.log(event);
    
    let items = event.data.items;
    let totals = event.data.totals;
    let recurringTotals = event.data.recurring_totals;

    updateItemsTable(items);
    updateSummaryTable(totals, recurringTotals);
  }

  function updateItemsTable(items) {
    const itemsTableBody = document.querySelector('.items-table tbody');
    itemsTableBody.innerHTML = '';

    items.forEach(item => {
      const newRow = createTableRow(item.product.name, item.price_name, item.quantity, item.totals.subtotal);
      itemsTableBody.appendChild(newRow);
    });
  }

  function createTableRow(productName, priceName, quantity, total) {
    const newRow = document.createElement('tr');
    newRow.innerHTML = `
      <td>${productName}</td>
      <td>${priceName}</td>
      <td>${quantity}</td>
      <td>${total.toFixed(2)}</td>
    `;
    return newRow;
  }

  function updateSummaryTable(totals, recurringTotals) {
    document.getElementById('oneTimeTotal').textContent = (totals.subtotal - recurringTotals.subtotal).toFixed(2);
    document.getElementById('recurringTotal').textContent = recurringTotals.subtotal.toFixed(2);
    document.getElementById('discountTotal').textContent = totals.discount.toFixed(2);
    document.getElementById('taxTotal').textContent = totals.tax.toFixed(2);
    document.getElementById('totalToday').textContent = totals.total.toFixed(2);
  }
    
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    },
    eventCallback: updateTable
  });
  
  // define items
  let monthItemsList = [
    {
      priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  let yearItemsList = [
    {
      priceId: 'pri_01gsz8z1q1n00f12qt82y31smh',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz96z29d88jrmsf2ztbfgjg',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  
  // open checkout
  function openCheckout(items){
    Paddle.Checkout.open({
      items: items
    });
  }
</script>
```

### Update items

Next, we'll build a function to replace items on our checkout

In our sample, we created a function called `switchPlan()`. Here's how it works:

1. We create a variable called `isMonthly` and set it to `true`. This variable tracks whether the current plan is monthly or yearly, and it's set to `true` initially because we pass `monthItemsList` to our checkout when the page loads.
2. We create a function called `switchPlan()`, then set a variable called `updatedItems` to our `yearItemList` array if `isMonthly` is `true`, and `monthItemList` if `isMonthly` is `false`.
3. We call the [`Paddle.Checkout.updateCheckout()`](https://developer.paddle.com/paddlejs/methods/paddle-checkout-updatecheckout.md) method, passing `updatedItems` as the `items` parameter. This is our yearly items list when the plan is monthly, and our monthly items list when the plan is yearly.
4. We toggle the value of `isMonthly`. If it were `true`, it's set to `false`; if it were `false`, it's set to `true`. This means that next time `switchPlan()` is called, it'll switch plans correctly.

```html {% highlightLines="97-106" collapse=true %}
<script type="text/javascript">
  function updateTable(event) {
    if (!event.name) {
      return;
    }
    
    console.log(event);
    
    let items = event.data.items;
    let totals = event.data.totals;
    let recurringTotals = event.data.recurring_totals;

    updateItemsTable(items);
    updateSummaryTable(totals, recurringTotals);
  }

  function updateItemsTable(items) {
    const itemsTableBody = document.querySelector('.items-table tbody');
    itemsTableBody.innerHTML = '';

    items.forEach(item => {
      const newRow = createTableRow(item.product.name, item.price_name, item.quantity, item.totals.subtotal);
      itemsTableBody.appendChild(newRow);
    });
  }

  function createTableRow(productName, priceName, quantity, total) {
    const newRow = document.createElement('tr');
    newRow.innerHTML = `
      <td>${productName}</td>
      <td>${priceName}</td>
      <td>${quantity}</td>
      <td>${total.toFixed(2)}</td>
    `;
    return newRow;
  }

  function updateSummaryTable(totals, recurringTotals) {
    document.getElementById('oneTimeTotal').textContent = (totals.subtotal - recurringTotals.subtotal).toFixed(2);
    document.getElementById('recurringTotal').textContent = recurringTotals.subtotal.toFixed(2);
    document.getElementById('discountTotal').textContent = totals.discount.toFixed(2);
    document.getElementById('taxTotal').textContent = totals.tax.toFixed(2);
    document.getElementById('totalToday').textContent = totals.total.toFixed(2);
  }
    
  Paddle.Environment.set("sandbox");
  Paddle.Initialize({
    token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
    checkout: {
      settings: {
        displayMode: "inline",
        frameTarget: "checkout-container",
        frameInitialHeight: "450",
        frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
      }
    },
    eventCallback: updateTable
  });
  
  // define items
  let monthItemsList = [
    {
      priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  let yearItemsList = [
    {
      priceId: 'pri_01gsz8z1q1n00f12qt82y31smh',
      quantity: 10
    },
    {
      priceId: 'pri_01gsz96z29d88jrmsf2ztbfgjg',
      quantity: 1
    },
    {
      priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
      quantity: 1
    }
  ];
  
  // open checkout
  function openCheckout(items){
    Paddle.Checkout.open({
      items: items
    });
  }
  
  // switch plan
  let isMonthly = true;

  function switchPlan() {
    let updatedItems = isMonthly ? yearItemsList : monthItemsList;
    Paddle.Checkout.updateCheckout({
      items: updatedItems
    });
    isMonthly = !isMonthly;
  }
</script>
```

### Add a button to swap plan

Finally, add a button to our HTML to call our `switchPlan()` function.

```html
<a href="#" onclick="switchPlan()"><b>Switch plan</b></a>
```

### Test your work

Save your page, then open it in your browser. Paddle Checkout should load as before. When you click the switch plan button, Paddle.js swaps items on your checkout from monthly to annual, and vice versa.

## Next steps

That's it. Now you've built a checkout, you might like to extend Paddle Checkout by presenting other fields to your checkout, automatically applying a discount, passing optional checkout settings, or building a success workflow.

### Add other fields to your checkout

Events emitted by Paddle.js contain information about the items and totals on a checkout. We present fields in `data.items[]`, `data.product`, `data.totals`, and `data.recurring_totals` in our sample.

You might like to include other data from the event on your page.

### Automatically apply a discount

Extend your checkout by passing a discount. When our checkout is launched, Paddle automatically applies the discount (where it's valid).

{% card-group cols=2 %}
{% card title="Prefill checkout properties" url="/build/checkout/prefill-checkout-properties" %}
Learn how to pass customer or business info (like email, name, or address) to Paddle Checkout.
{% /card %}
{% card title="Create or update a discount" url="/build/products/offer-discounts-promotions-coupons" %}
Set up and manage discount codes or time-limited promotions for your products.
{% /card %}
{% /card-group %}

### Pass checkout settings

We covered passing the required settings for inline checkout, but there are a bunch of other settings you can pass that give you more control over how opened checkouts work. For example, you can set the language that Paddle Checkout uses, hide the option to add a discount, or restrict payment methods shown to customers.

{% card-group cols=2 %}
{% card title="Pass checkout settings" url="/build/checkout/set-up-checkout-default-settings" %}
See all the settings you can pass to configure your inline checkout.
{% /card %}
{% card title="Paddle.Checkout.open() method" url="/paddlejs/methods/paddle-checkout-open" %}
Full API documentation for opening and customizing checkouts.
{% /card %}
{% /card-group %}

### Build a success workflow

When customers complete checkout, Paddle Checkout has a final screen that lets customers know that their purchase was successful. If you like, you can redirect customers to your own page or use JavaScript event callbacks to build a more advanced success workflow.

{% card-group cols=2 %}
{% card title="Handle checkout success" url="/build/checkout/handle-success-post-checkout" %}
Redirect customers or add logic to handle post-checkout workflows.
{% /card %}
{% card title="Paddle.js events reference" url="/paddlejs/events/overview" %}
See all of the events you can listen to for customizing your checkout experience.
{% /card %}
{% /card-group %}