Build a pricing page
Get a step-by-step overview of how to build a pricing page that displays localized prices, including taxes and discount calculation. Open a checkout when a prospect wants to sign up.
Pricing pages show prospects the subscription plans, addons, or one-time charges that you offer and how much they cost. They're one of the most important pages on your website, and typically play a key part in customer conversion.
You can use Paddle.js to build pricing pages that show prospects prices that are relevant for their country, displayed in their local currency with estimated taxes. If you're running a sale or promo, you can calculate discounts too.
Grab the code and test using CodePen
CodePen is a platform for building and sharing frontend code. Explore the code for this tutorial and test right away using our pricing page pen.
How it works
Paddle Checkout automatically shows the correct prices for a customer using geolocation to estimate where a customer is buying from. Customers see prices in their local currency, with taxes estimated for their country or region.
You can use the Paddle.PricePreview()
method in Paddle.js to get localized prices for pricing pages or other pages on your website. This means you can show the same information on your pricing page that a customer sees when they open checkout to subscribe.
You don't need to do any calculations yourself or manipulate returned data. Paddle returns totals formatted for the country or region you're working with, including the currency symbol.
What are we building?
In this tutorial, we'll create a simple, three-tier pricing page. It includes a toggle to switch between monthly and annual plans, and a country selector. Our page updates when customers switch billing cycle or change country.
We'll learn how to:
- Include and set up Paddle.js using a client-side token
- Build an items list that we can send to
Paddle.PricePreview()
- Present and update prices on our page
- Toggle between monthly and annual prices for products
- Pass a location to Paddle.js to get prices for a country
If you like, you can copy-paste the sample code into your editor or view on CodePen and follow along.
12345678910111213141516171819201<!DOCTYPE html>
2<html lang="en" color-mode="user">
3<head>
4 <title>Pricing page demo</title>
5 <meta charset="utf-8"/>
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
8 <style>
9 .pricing-page-container {
10 max-width: 900px;
11 margin: auto;
12 text-align: center;
13 margin-top: 2em;
14 padding-left: 1em;
15 padding-right: 1em;
16 }
17 .pricing-grid {
18 display: block;
19 margin-bottom: 1em;
20 }
Before you begin
Choose a pricing page
This tutorial walks through creating a simple pricing page. You can also create a cart-style pricing page for more advanced implementations using transaction previews.
Simple pricing page
Pass a batch of price IDs and location information to Paddle.js. Paddle returns localized pricing for each item.
- Recommended for most pricing pages. Simply returns localized prices.
- Uses
Paddle.PricePreview()
. - Requests and responses mirror the preview prices operation.
- Returns item totals formatted for the currency and region, including currency code.
- Response only includes calculations for each item included in the request.
- You can send prices with different billing periods and trial periods.
Cart-style pricing page
Send a batch of price IDs and location information to Paddle.js. Paddle returns a preview of a transaction.
- Recommended for more advanced pricing pages where users can build their own plans.
- Requests and responses mirror the preview a transaction operation.
- Returns item totals in the lowest denomination for a currency (e.g. cents for USD).
- Response includes calculations for line items and overall totals.
- Requests mirror creating a transaction, so billing and trial periods must match for all items.
Create products and prices
You'll need to create a product and at least one related price for the items that you want to include on your pricing page.
Localize prices
To show localized prices, turn on automatic currency conversion or add price overrides to your prices.
Get started
To build a pricing page:
Include and initialize Paddle.js
Add Paddle.js to your app or website, so you can securely work with your product catalog.
Build a pricing preview request body and pass to
Paddle.PricePreview()
.Update your page based on the response
Present information returned by Paddle.js to a customer on your page.
Extend by adding location information
Capture location information and pass to Paddle.js to get localized prices for a country.
1. Include and initialize Paddle.js
Paddle.js 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 by adding this script to the <head>
:
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
Set environment (optional)
We recommend signing up for a sandbox account 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, call Paddle.Environment.set()
and set your environment to sandbox
:
12341<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
2<script type="text/javascript">
3 Paddle.Environment.set("sandbox");
4</script>
Pass a client-side token
Next, go to Paddle > Developer tools > Authentication and generate a client-side token. Client-side tokens 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()
and pass your client-side token as token
. For best performance, do this just after calling Paddle.Environment.set()
, like this:
12345671<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
2<script type="text/javascript">
3 Paddle.Environment.set("sandbox");
4 Paddle.Initialize({
5 token: "test_7d279f61a3499fed520f7cd8c08" // replace with a client-side token
6 });
7</script>
Client-side tokens are separate for your sandbox and live accounts. You'll need to generate a new client-side token for your live account. Sandbox tokens start with
test_
to make them easy to distinguish.
2. Pass prices to Paddle.js
Next, we'll pass prices to Paddle.js so that we can get localized prices for them. When previewing prices, Paddle returns calculated totals for line items only — it doesn't include overall totals. This means that we can include prices with different billing cycles and trial periods in our request, unlike when opening a checkout or creating a transaction.
Define lists of prices
Our page includes four prices:
Starter
- Starter (monthly)
- Starter (yearly)
Pro
- Pro (monthly)
- Pro (yearly)
In Paddle, we've set these up as two products called 'Starter' and 'Pro', each with two prices for monthly and annual.
This is an example from the list products operation in the Paddle API. It shows the two products we're using, including an array of prices for each.
12345678910111213141516171819201{
2 "data": [
3 {
4 "id": "pro_01gsz4t5hdjse780zja8vvr7jg",
5 "name": "ChatApp Pro",
6 "tax_category": "standard",
7 "description": "Everything in starter, plus access to a suite of powerful tools and features designed to take your team's productivity to the next level.",
8 "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png",
9 "custom_data": null,
10 "status": "active",
11 "created_at": "2023-02-23T12:43:46.605Z",
12 "prices": [
13 {
14 "id": "pri_01gsz8z1q1n00f12qt82y31smh",
15 "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg",
16 "description": "Annual (per seat)",
17 "name": null,
18 "billing_cycle": {
19 "interval": "year",
20 "frequency": 1
To define these, create variables for your products in your script section and set them to the Paddle IDs for your products. We'll use these later to determine which products returned prices are for.
Then, create arrays for your prices. Each array should contain an object that includes the Paddle ID for a price (priceId
) and a quantity
. We've created two arrays:
monthItems
, which contains monthly prices for our products.yearItems
, which contains yearly prices for our products.
We'll present localized prices for monthItems
when the monthly toggle is selected, and yearItems
when the yearly toggle is selected.
12345678910111213141516171819201<script type="text/javascript">
2 Paddle.Environment.set("sandbox");
3 Paddle.Initialize({
4 token: 'test_7d279f61a3499fed520f7cd8c08' // replace with a client-side token
5 });
6
7 // define products and prices
8 var starterProduct = 'pro_01gsz4s0w61y0pp88528f1wvvb';
9 var proProduct = 'pro_01gsz4t5hdjse780zja8vvr7jg';
10 var monthItems = [{
11 quantity: 1,
12 priceId: 'pri_01gsz8ntc6z7npqqp6j4ys0w1w',
13 },
14 {
15 quantity: 1,
16 priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
17 }
18 ];
19 var yearItems = [{
20 quantity: 1,
Get prices
Next, we'll create a function to get prices. This should pass our list of monthly or yearly items to Paddle.js.
In our sample, we've created a function called getPrices()
that takes a parameter called cycle
. Here's how it works:
We create a variable called
billingCycle
and set this toyear
. This is the billing cycle that we'd like to show when customers first visit our page.We check to see if
cycle
ismonth
, then set a variable calleditemsList
to eithermonthItems
oryearItems
. We also set a variable calledbillingCycle
to the value ofcycle
for later.We define a variable called
request
. This is what we're going to send to Paddle.js. It includes an object with anitems
key. The format of our request should match the request body for the pricing preview operation in the Paddle API, except withcamelCase
names for fields.We call
Paddle.PricePreview()
, passing inrequest
as a parameter.Paddle.PricePreview()
returns a promise that contains a pricing preview object. We use the.then()
method to attach a callback that logs the resolved value to the console, and the.catch()
method to log errors to the console.
202122232425262728293031323334353637383920 quantity: 1,
21 priceId: 'pri_01gsz8s48pyr4mbhvv2xfggesg',
22 },
23 {
24 quantity: 1,
25 priceId: 'pri_01gsz8z1q1n00f12qt82y31smh',
26 }
27 ];
28
29 // set initial billing cycle
30 var billingCycle = 'year'
31
32 // get prices
33 function getPrices(cycle) {
34 var itemsList = cycle === "month" ? monthItems : yearItems;
35 var billingCycle = cycle;
36 var request = {
37 items: itemsList
38 }
39
Test your work
Save your page, then open your browser console and type getPrices('year')
or getPrices('month')
. You should see a promise that contains a pricing preview object from Paddle returned the console.
Use
⌘ Command
+⌥ Option
+J
(Mac) orCtrl
+⇧ Shift
+J
(Windows) to quickly open your browser console in Google Chrome.
3. Update page
Our function doesn't do anything to our page yet. We'll update getPrices()
so that it displays pricing information returned by Paddle.js on our page.
Create HTML for pricing table
First, we need to add some HTML for a simple pricing table with options for monthly and yearly. We'll add some CSS to the <head>
of the page, too.
Add this to the <body>
of your page.
In this sample, we have radio buttons for our pricing toggle, then a <div>
with three <div>
elements for each product that we offer. The radio buttons have an onclick
attribute that runs our getPrices()
function when clicked, passing either month
or year
as the parameter for cycle
.
There are id
s set on the <p>
elements that contain prices. We'll use these IDs to replace the contents of these elements with returned prices from Paddle.js later.
12345678910111213141516171819201<div class="pricing-page-container">
2 <h1>Choose your plan</h1>
3 <div class="pricing-toggle">
4 <input type="radio" name="plan" value="month" id="month" onclick="getPrices('month')"><label for="month">Monthly</label>
5 <input type="radio" name="plan" value="year" id="year" onclick="getPrices('year')" checked><label for="year">Yearly <sup>save 20%</sup></label>
6 </div>
7 <div class="pricing-grid">
8 <div class="starter-plan">
9 <h3>Starter</h3>
10 <p id="starter-price">$100.00</p>
11 <p><small>per user</small></p>
12 <button>Sign up now</button>
13 </div>
14 <div class="pro-plan">
15 <h3>Pro</h3>
16 <p id="pro-price">$300.00</p>
17 <p><small>per user</small></p>
18 <button>Sign up now</button>
19 </div>
20 <div class="enterprise-plan">
Update elements using JavaScript
Next, we'll change our script to update the starter-price
and pro-price
elements so they return pricing from Paddle.
First, we'll get elements in our pricing table using their id
and assign them to variables that we can use later.
Then, we'll update our getPrices()
function to iterate through result.data.details.lineItems
. This array contains calculated totals for the prices that we passed to Paddle.js.
To make sure we show the correct prices for our products, we check to see if the Paddle ID of the related product of a price matches the product IDs we defined earlier:
- If the product for a returned price is
starterProduct
, we replace the contents of thestarter-price
element withitem.formattedTotals.subtotal
- If the product for a returned price is
proProduct
, we replace the contents of thepro-price
element withitem.formattedTotals.subtotal
.
For this sample, we also log item.formattedTotals.subtotal
to console. This can be useful for debugging.
202122232425262728293031323334353637383920 quantity: 1,
21 priceId: 'pri_01gsz8s48pyr4mbhvv2xfggesg',
22 },
23 {
24 quantity: 1,
25 priceId: 'pri_01gsz8z1q1n00f12qt82y31smh',
26 }
27 ];
28
29 // DOM queries
30 var starterPriceLabel = document.getElementById("starter-price");
31 var proPriceLabel = document.getElementById("pro-price");
32
33 // set initial billing cycle
34 var billingCycle = 'year'
35
36 // get prices
37 function getPrices(cycle) {
38 var itemsList = cycle === "month" ? monthItems : yearItems;
39 var billingCycle = cycle;
Set getPrices() to run on page load
Right now, our function only runs when the monthly or annual radio buttons are clicked.
We can add onLoad
to our <body>
tag to run our getPrices()
function immediately after the page has loaded:
11<body onLoad="getPrices(billingCycle)">
Test your work
Save your page, then open it in your browser. You should see prices from Paddle.js in your pricing table. Selecting the monthly or annual toggle should change the prices that you see.
4. Extend by adding location information
Paddle supports 30 currencies across more than 200 countries and territories, automatically handling tax and price localization for you.
At the moment, our page shows the base price for our prices. We can pass location information to Paddle.js to present customers with prices in their local currency with estimated tax calculations.
There are three ways we can pass location information to Paddle.js:
Country code and (optionally) ZIP/postal code
We can include an
address
object containingcountryCode
and (optionally)postalCode
. Paddle uses the supplied country and ZIP/postal code (where included) to estimate tax and localized pricing.IP address
Capture a visitor IP address, then include
customerIpAddress
. Services like ipify can return an IP address as text, JSON, or JSONP. Paddle fetches location using the IP address to estimate tax and localized pricing.Pass existing Paddle IDs
Pass
customerId
,addressId
, andbusinessId
for existing entities. This is generally used for logged-in customers who may be considering other subscriptions, or as part of an upgrade workflow. Paddle uses existing customer entities to estimate tax and localized pricing.
For this tutorial, we'll send a country code.
Create HTML for country drop-down
First, let's add some HTML for a drop-down box under our pricing table. This sample includes a selection of countries, but you can add options for any country that Paddle supports. The value for each option
corresponds to the two-letter country code for that country.
1819202122232425262728293031323334353618 <button>Sign up now</button>
19 </div>
20 <div class="enterprise-plan">
21 <h3>Enterprise</h3>
22 <p>Contact us</p>
23 <p><small>bespoke pricing</small></p>
24 <button>Inquire now</button>
25 </div>
26 </div>
27 <div class="country-selector">
28 <select name="country" id="country" autocomplete="off">
29 <option value="US">🇺🇸 United States</option>
30 <option value="GB">🇬🇧 United Kingdom</option>
31 <option value="ES">🇪🇸 Spain</option>
32 <option value="IN">🇮🇳 India</option>
33 <option value="US">🌍 Other</option>
34 </select>
35 </div>
36</div>
Pass location to getPrices()
Next, we need to pass the value
of the chosen option in the drop-down box to Paddle. To do this:
We create a variable called
billingCountry
and set this toUS
. This is the billing cycle that we'll show customers when the page loads initially.We add an event listener that occurs when the value of the dropdown is changed. It updates our
billingCountry
variable to thevalue
of the dropdown, then runs ourgetPrices()
function.We update our
request
to include anaddress
object that containscountryCode
, set to the value ofbillingCountry
.
272829303132333435363738394041424344454627 ];
28
29 // DOM queries
30 var starterPriceLabel = document.getElementById("starter-price");
31 var proPriceLabel = document.getElementById("pro-price");
32
33 // set initial billing cycle
34 var billingCycle = 'year'
35
36 // set initial country
37 var billingCountry = 'US';
38
39 // choose country
40 var dropdown = document.getElementById("country");
41 dropdown.addEventListener("change", function() {
42 billingCountry = dropdown.value;
43 console.log("country changed: " + billingCountry);
44 var plan = document.querySelector("input[type=radio][name=plan]:checked");
45 getPrices(plan.value)
46 });
Test your work
Save your page, then open it in your browser. You should see prices from Paddle.js in your pricing table. Selecting the monthly or annual toggle should change the prices that you see. Choosing a country from the drop-down menu should change the currency and prices that you see.
Not seeing localized prices? Add price overrides to your prices, or turn on automatic currency conversion in Paddle > Business account > Currencies. See: Localize prices
Next steps
That's it! Now we've built a simple pricing page, you might like to add other fields to your page, pass a discount, or open a checkout.
Add other fields to your pricing page
Paddle.PricePreview()
returns a pricing preview object for the prices and location passed. We show details.lineItems.formattedTotals.subtotal
in our sample. This is the calculated total for an item before estimated taxes and discounts, formatted for a particular currency.
You might like to use another value for the price you show on your page, or include other values.
Here are some fields in the response that you might like to use on your page:
details.address.countryCode | Country code for the pricing preview. If you sent an IP address, Paddle returns the detected country. |
details.line_items[].formattedTotals | Totals for a particular line item, formatted as a string for the currency you're working with. |
details.line_items[].formattedUnitTotals | Totals for one unit of a particular line item, formatted as a string for the currency you're working with. |
details.line_items[].price.trialPeriod | Details of the trial period for a price. |
details.line_items[].discounts[].formattedTotal | Total amount discounted for a discount applied to a line item, formatted as a string for the currency you're working with. |
For a full list of values, see: Pricing preview object
Pass a discount
Extend your pricing page by passing discountId
in your request to Paddle.PricePreview()
. The response includes a discount
array that has information about the discount applied. Calculated totals in details.lineItems
include discounts, where applicable.
Open a checkout
Pass items to Paddle.Checkout.open()
or use HTML data attributes to open a checkout.
- Build a pricing page
- How it works
- What are we building?
- Before you begin
- Choose a pricing page
- Create products and prices
- Localize prices
- Get started
- 1. Include and initialize Paddle.js
- Include Paddle.js script
- Set environment (optional)
- Pass a client-side token
- 2. Pass prices to Paddle.js
- Define lists of prices
- Get prices
- Test your work
- 3. Update page
- Create HTML for pricing table
- Update elements using JavaScript
- Set getPrices() to run on page load
- Test your work
- 4. Extend by adding location information
- Create HTML for country drop-down
- Pass location to getPrices()
- Test your work
- Next steps
- Add other fields to your pricing page
- Pass a discount
- Open a checkout