More Tutorials

Subscription Enrollment


Tutorial Scope

The guide below helps you build your desired Subscription Enrollment experience on your product detail pages (PDP) with Chargebee API.

Subscription Enrollment in e-commerce allows customers to subscribe to products or services directly from a store’s product detail page (PDP) or promotional product landing page. Chargebee's Subscription Enrollment functionality facilitates a seamless shopping experience customers, allowing them choose subscription frequency easily, view pricing information, understand savings, and decide between subscription or one-time purchase options.

Supported Subscription Models

Chargebee enables brands to build the following product subscription models listed below facilitate optimal revenue growth.

  • Pay-Per-Delivery Subscriptions
  • Pre-Pay Subscriptions
  • Membership Subscriptions
  • Gift Subscriptions
  • Digital Subscription + Hardware Bundle (IoT)


The Pay-Per-Delivery model is utilized by brands that desire their customers to pay for their subscription before the product is sent to the customer. The pay-per-delivery model is often paired with customized promotions like subscribe-and-save discounts to encourage customers to purchase a subscription at a discount instead of purchasing the product as a one-time purchase.

Pre-Pay Subscriptions

The Pre-Pay model is utilized by brands that desire their customers to pay for their subscription upfront for a period (i.e., annually), and the customer gets a product delivery every month. The pre-pay model is often paired with customized promotions like subscribe-and-save discounts to encourage customers to purchase a subscription at a discount instead of purchasing the product as a one-time purchase.

Membership Subscriptions

The Membership model is utilized by brands that desire to launch a paid membership program for their brands—paid memberships programs often product the shopper with free shipping, members-only pricing, and cash back to encourage repeat business.

Gift Subscriptions

Gift Subscriptions are utilized by brands that desire to generate revenue by allowing your customers to gift subscriptions to family and friends. Gifts could be sent to recipients immediately on sign-up or scheduled for special occasions such as holidays or birthdays.

Digital Subscription + Hardware (One-time purchase) Bundles

Digital Subscription + Hardware bundles are often utilized by IoT brands selling hardware embedded with software, sensors, or other technologies paired with a digital service subscription with the purchase of keeping the hardware device connected and exchanging data with other devices or systems over the internet.

Feature Menu: Subscription Enrollment

The following is the list of feature menus for subscription enrollment:

  • View and Select Product Variants
  • View Product Purchasing Options
  • Delivery Frequency Selector
  • Product Variant Price per frequency
  • Subscription Description (dynamic per product variant/plan selected)
  • Call to Action button (For example, Add to Cart, Add Subscription, and Add Membership)

API Tutorial: Subscription Enrollment

Chargebee Setup

To set up Chargebee for Subscription Enrollment, here are the steps.

  1. Sign in or Sign up to your Chargebee account.
  2. Configure multi-currency pricing if required.
  3. Configure product taxes.
  4. Configure Customer or Subscription custom fields if required.
  5. Enable and Create product details, including product options and variants
    • Create bulk Products / Variants in Chargebee.
    • Use your third-party Variant Pricing with Chargebee.
  6. Create a plan, addon, or charge and adding an existing product.
  7. Enable Gift Subscriptions if required.

Feature Menu Implementation Guide using API

The following is an API implementation guide for building your desired Subscription Enrollment experience for your store.

Subscription Enrollment
Subscription Enrollment

PDP Widget Creation

A PDP (Product Detail Page) widget is a user interface element designed to showcase specific product information and options to customers on an e-commerce website. In this demo PDP widget, the focus is on providing options related to product variants, subscription frequency, quantity, and actions such as "Add to Cart" and "Subscribe Now." It allows customers to interact with the widget and choose their preferred options before making a purchase or subscribing to the product.

Here's a breakdown of the options available in the demo PDP widget:

  1. Product Variant: Customers can select from different product variants, such as size, color, or any other product attribute that offers variation. The dropdown button displays the available options, and customers can choose the variant that matches their preferences.
  2. Subscription Frequency with pricing: This dropdown button provides customers with options for different subscription frequencies with the respective price. The available choices are:
    • One-Time Purchase: Customers can select this option if they want to make a one-time purchase without any recurring commitment.
    • Yearly: Customers can choose to subscribe to the product and receive it on a yearly basis.
    • Half-Yearly: Customers can opt for a subscription with deliveries scheduled every six months.
    • Quarterly: This option allows customers to receive the product every three months through a subscription.
    • Monthly: Customers can select this option to subscribe to a monthly delivery of the product.
  3. Quantity: Customers can input the desired quantity of the product they want to purchase or receive in each delivery if they choose a subscription frequency. The input field allows them to specify the exact quantity they need.
  4. Add to Cart: By clicking the "Add to Cart" button, customers can add the selected product variant and subscription preferences to their shopping cart for future checkout. This option is suitable for customers who prefer one-time purchases.
  5. Subscribe Now: The "Subscribe Now" button allows customers to proceed with subscribing to the product with the chosen subscription frequency and quantity. On clicking this button, the page will redirect to the Checkout flow. Once checkout is completed, the product will be added to their subscription list, and they will receive it based on the selected frequency.


The above PDP demo is only an example. All elements in the demo is holding dummy data and does not request any Chargebee APIs. To create your own product details page please implement these frontend and backend codes.

Creating a PDP Widget

The following code sample is for the above PDP demo example:


  • Install Node js version 14 or above.
  • Please note that the following instructions consider that you have the required API key and other necessary credentials to use the Chargebee APIs.

Steps to build the PDP widget

The following are the steps to build PDP widget using code snippets.


Step 1: Build HTML for Widget

Create a new folder such as subscription-enrollment-example, and create a new HTML file named cb-widget.html and paste the following code snippet into it. You can modify the appearance of the widget by modifying the CSS variables.

cb-widget.htmlView full code
<!DOCTYPE html>
    <title>PDP Widget</title>
      body {
        min-height: 100vh;
        CSS Variables - Configure styling by modifying these variables
      .cb-widget-container {
        --cb-font-family: system-ui, -apple-system, BlinkMacSystemFont,
          'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
          'Helvetica Neue', sans-serif;
        --cb-font-size: 14px;
        --cb-font-color: #012a38;
        --cb-border-radius: 4px;
        --cb-box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
        --cb-widget-bg: #fff;
        --cb-cta-bg: #012a38;
        --cb-cta-text: #fff;
        --cb-variant-bg: #fff;
        --cb-variant-active-bg: #137cb6;
        --cb-variant-border: 1px solid #012a38;
      .cb-loading-container {
        text-align: center;
        margin-top: 180px;
      .cb-button-disabled {
        pointer-events: none;
        cursor: not-allowed;
        opacity: 0.5;
      .cb-widget-container {
        position: fixed;
        min-height: 400px;
        width: 400px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        max-width: 400px;
        padding: 30px 20px;
        border: 1px solid #ccc;
        border-radius: var(--cb-border-radius);
        font-family: var(--cb-font-family);
        font-size: var(--cb-font-size);
        color: var(--cb-font-color);
        box-shadow: var(--cb-box-shadow);
        background-color: var(--cb-widget-bg);
      .cb-widget-title {
        margin-top: 0;
      .cb-frequency-select select,
      .cb-quantity {
        margin-bottom: 10px;
      .cb-quantity {
        padding-top: 20px;
      .cb-quantity label {
        margin-right: 20px;
      .cb-quantity button {
        border: none;
        padding: 5px 10px;
      .cb-quantity-wrapper {
        display: inline-block;
        border: 1px solid gray;
      .cb-quantity-wrapper span {
        margin: 0 15px;
      .cb-frequency-select select,
      .cb-variant-select {
        display: block;
        margin-top: 10px;
        width: 100%;
        height: 36px;
        padding: 5px;
        border-radius: var(--cb-border-radius);
      .cb-cta-button {
        padding: 10px 20px;
        background-color: var(--cb-cta-bg);
        color: var(--cb-cta-text);
        border-radius: var(--cb-border-radius);
        text-decoration: none;
        display: block;
        text-align: center;
        margin-top: 10px;
      .cb-variant-selector {
        padding: 10px 0;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-evenly;
      .cb-variant-selector button {
        margin-top: 10px;
        padding: 10px 16px;
        border-radius: var(--cb-border-radius);
        border: var(--cb-variant-border);
        background: var(--cb-variant-bg);
        cursor: pointer;
      .cb-variant-selector {
        background: var(--cb-variant-active-bg);
        color: #fff;
        border: 0;
      .cb-cta-container {
        margin-top: 30px;
    <!-- Widget Wrapper Container starts -->
    <div class="cb-widget-container">
      <!-- Loading Container starts -->
      <div class="cb-loading-container" style="display: block">Loading...</div>
      <!-- Loading Container ends -->
      <!-- Widget Main Container starts -->
      <div class="cb-widget-loaded" style="display: none">
        <!-- Widget Title -->
        <h2 class="cb-widget-title">PDP Widget</h2>
        <!-- Variant Selector starts -->
        <div class="cb-variant-select-wrapper">
          <label for="cb-variant">Select Variant:</label>
          <div class="cb-variant-selector">
            <button class="cb-variant">Variant 1</button>
            <button class="cb-variant">Variant 2</button>
            <button class="cb-variant">Variant 3</button>
        <!-- Variant Selector ends -->
        <!-- Frequency Selector starts -->
        <div class="cb-frequency-select">
          <label for="cb-frequency">Choose Purchase Option:</label>
          <select id="cb-frequency">
            <option value="monthly">Monthly</option>
            <option value="quarterly">Quarterly</option>
            <option value="annually">Annually</option>
        <!-- Frequency Selector ends -->
        <!-- Quantity Selector starts -->
        <div class="cb-quantity">
          <label for="cb-quantity-input">Quantity:</label>
          <div class="cb-quantity-wrapper">
            <button type="button" class="cb-decrement-btn">-</button>
            <span id="cb-quantity-input">1</span>
            <button type="button" class="cb-increment-btn">+</button>
        <!-- Quantity Selector ends -->
        <div class="cb-subs-description"></div>
        <!-- CTA Wrapper starts -->
        <div class="cb-cta-container">
          <!-- Add click event for Add to Cart to add your cart logic -->
          <a class="cb-cta-button" id="cb-add-to-cart" href="javascript:void(0)"
            >Add to Cart</a
          <a class="cb-cta-button" id="cb-checkout" href="#">Subscribe Now</a>
        <!-- CTA Wrapper ends -->
      <!-- Widget Main Container ends -->
    <!-- Widget Wrapper Container ends -->
    <script src="/static/cb-widget.js"></script>

Step 2: Add Client-side functionality

The Initialization of Chargebee widget requires few properties as stated below:

Property Description Allowed values
product_id* Provide the product ID for which the widget should be displayed. This is a mandatory property. Accepts: string
currency* Provide the Currency code of the store. Refer supported Currency codes by Chargebee. This is a mandatory property. Accepts: string
Default value: USD
variantSelector Determines how the variants should be displayed. It can either be displayed as group of Buttons or Select dropdown. This is an optional property. Accepts: button/select
Default value: select
showAddToCart Determines whether to display the Add to cart CTA. This is an optional property. Accepts: true/false
Default value: true
showSubscribeNow Determines whether to display the Subscribe Now CTA. This is an optional property. Accepts: true/false
Default value: true
  product_id: 'PRODUCT_ID',
  variantSelector: 'select',
  currency: 'USD',
  showAddToCart: true,
  showSubscribeNow: true

Create a new javascript file named cb-widget.js and paste the following code snippet into it. Modify the CbWidget.init call with appropriate product_id and currency.

cb-widget.jsView full code
// CbWidget contains all the logic required to build the Widget
const CbWidget = {
  inited: false,
  options: {
    site_id: '', // Mandatory field - Site ID
    customer_id: '', // Optional
    product_id: '', // Mandatory
    variantSelector: 'select', // button/select
    currency: 'USD', // Valid Currency Code
    showAddToCart: true, // Display Add to Cart in your widget
    showSubscribeNow: true // Display Subscribe now in your widget
  variants: [],
  quantity: 1,
  prices: {
    subscriptionPrices: [],
    oneTimePrices: []
  widgetData: {}, // Populate data to this property and use it to build widget
  selectors: {
    loadingContainer: document.getElementsByClassName(
    loadedContainer: document.getElementsByClassName('cb-widget-loaded')[0],
    variantSelector: document.getElementsByClassName('cb-variant-selector')[0]
  // Utility to make API call to your backend
  fetchCBApi: async function (url, options = {}) {
    const response = await fetch(url, options);
    return response.json();
  // initialize CbWidget. Entry point to get started
  init: async function (options = {}) {
    // Override default options with the custom options
    this.options = {
    // Fetch APIs required to populate data in widgetData
    await this.retrieveData();
    // Use the data and options provided to construct the widget data
    // Using the widgetData render the widget on the page
    // Trigger click/change event on Variant selector to pre-select the first Variant by default
    if (this.options.variantSelector === 'button') {
      document.querySelector('.cb-variant-select-wrapper button').click();
    } else {
        .dispatchEvent(new Event('change'));
    // Add event handlers for '-' and '+' buttons of Quantity selectors
      .addEventListener('click', () => this.quantityModifier(-1));
      .addEventListener('click', () => this.quantityModifier(1));
    // Show blocks based on the options provided
    if (!this.options.showAddToCart) {
      this.toggleBlock('#cb-add-to-cart', true);
    if (!this.options.showSubscribeNow) {
      this.toggleBlock('#cb-checkout', true);
    } else {
      // Add event handler for Subscribe now
        .addEventListener('click', (e) => this.subscribeNow(e));
  retrieveData: async function () {
    let error = null;
    // Fetch Variant, Plans, Charges info
    const [variants, subscriptionPlans, oneTimeCharges] = await Promise.all([
      this.fetchCBApi('/api/variants?product_id=' + this.options.product_id),
        '/api/fetch-items?product_id=' + this.options.product_id + '&type=plan'
        '/api/fetch-items?product_id=' +
          this.options.product_id +
    ]).catch((err) => {
      // Show Error message on the widget
      error = err;
    // Fetch Subscription price and one time prices
    const [subscriptionPrices, oneTimePrices] = await Promise.all([
      this.fetchCBApi('/api/fetch-item-prices?item_id=' +,
      this.fetchCBApi('/api/fetch-item-prices?item_id=' +
    ]).catch((err) => {
      // Show Error message on the widget
      error = err;
    if (error) {
    // Hide loading message and display the Widget content
    this.inited = true; = 'none'; = 'block';
    // Populate the fetched data into the appropriate placeholders for easy reference
    this.variants = variants.list;
    this.prices.subscriptionPrices = subscriptionPrices;
    this.prices.oneTimePrices = oneTimePrices;
  constructWidgetData: function () {
    this.variants.forEach((variant) => {
      this.widgetData[] = {
        oneTimePrices: this.prices.oneTimePrices[],
        subscriptionPrices: this.prices.subscriptionPrices[]
  renderWidget: function () {
    // Construct Variant Selector
    this.selectors.variantSelector.innerHTML = '';
    // Filter the variants that have atleast one price configured
    const filteredVariants = Object.keys(this.widgetData).filter(
      (variantId) => {
        const variant = this.widgetData[variantId];
        return (
          variant.oneTimePrices?.length || variant.subscriptionPrices?.length
    // If the variant selector is configured as Select, Create Select containers
    if (this.options.variantSelector === 'select') {
      const select = document.createElement('select');
      select.classList = ['cb-variant-select'];
      // Each Variants have unique set of Frequency prices, Change frequencies whenever Variant is changed
      select.addEventListener('change', (e) => {
    filteredVariants.forEach((variantId, index) => {
      const variant = this.widgetData[variantId];
      // Create button containers if the variant selector is configured as Buttons
      if (this.options.variantSelector === 'button') {
        const button = document.createElement('button');
        button.value =;
        button.textContent =;
        if (index === 0) {
          button.className = 'active';
        // Each Variants have unique set of Frequency prices, Change frequencies whenever Variant is changed
        button.addEventListener('click', (e) => {
      } else {
        const option = document.createElement('option');
        option.value =;
        option.textContent =;
  populateFrequencies: function (e) {
    // Get the Variant Id
    const variantId =;
    // Toggle the active class to highlight the selected Variant in the UI
    if (this.options.variantSelector === 'button') {
      document.querySelector('.cb-variant-select-wrapper .active').className =
        ''; = 'active';
    const frequencySelector = document.querySelector('#cb-frequency');
    let frequencies = [];
    // Check if there are One time prices and populate them first
    const oneTime = this.widgetData[variantId].oneTimePrices?.find((price) => {
      return price.currency_code === this.options.currency;
    if (oneTime) {
    // Check if there are Subscription prices and populate them
    const subscriptions = this.widgetData[variantId].subscriptionPrices?.filter(
      (price) => {
        return price.currency_code === this.options.currency;
    if (subscriptions?.length) {
      frequencies = [...frequencies, ...subscriptions];
    frequencySelector.innerHTML = '';
    // After populating the prices and frequencies, build the Frequency selector
    frequencies.forEach((frequency) => {
      const option = document.createElement('option');
      option.value =;
      option.textContent = `${frequency.period_unit || 'One Time'} - ${
      } ${(frequency.price / 100).toFixed(2)}`;
      option.dataset.description = frequency.description || '';
    frequencySelector.addEventListener('change', (e) => {
    document.querySelector('#cb-frequency').dispatchEvent(new Event('change'));
  changeFrequency: function (e) {
    this.widgetData.selectedFrequency =;
    const subsDescription = document.querySelector('.cb-subs-description');
    subsDescription.innerHTML = '';
    const desc = document.querySelector(
      '#cb-frequency [value=' + + ']'
    subsDescription.innerHTML = desc ? desc : '';
  subscribeNow: async function (e) {
    try {
      const checkout = await this.fetchCBApi(
          method: 'POST'
      window.location.href = checkout.url;
    } catch (e) {
  quantityModifier: function (count) {
    if (this.quantity + count > 0) {
      this.quantity = this.quantity + count;
      document.querySelector('#cb-quantity-input').innerHTML = this.quantity;
  toggleBlock: function (selector, isHide) {
    if (!document.querySelector(selector)) {
    document.querySelector(selector).style = isHide
      ? 'display: none'
      : 'display: block';
  showError: function () {
    this.selectors.loadingContainer.innerHTML =
      'Sorry, Something went wrong. Try reloading the page.';
  site_id: 'SITE_ID',
  customer_id: 'CUSTOMER_ID',
  product_id: 'PRODUCT_ID',
  variantSelector: 'select', // select/button
  currency: 'USD' // 'USD', 'EUR', etc.,


Step 3: Create package.json for express server

Inside the subscription-enrollment-example folder, create a new file named package.json and paste the following code snippet into it:

package.jsonView full code
  "name": "subscription-enrollment-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chargebee": "^2.6.0",
    "cors": "^2.8.4",
    "express": "^4.16.2",
    "node-fetch": "^2.6.11"

Step 4: Install dependencies

Open a terminal, navigate to the ../subscription-enrollment-example folder path, and run npm install to install the dependencies.

Step 5: Create API routes to interact with Chargebee API

To connect your backend with Chargebee APIs, the following information is required:

Create a new javascript file named index.js and paste the following code snippet into it. Add your store's site name and full access API key to this file.


  • CORS (Cross-Origin Resource Sharing) is enabled only for demo purposes and is not recommended to use in the production server unless you understand CORS thoroughly and handle it appropriately. Remember to replace YOUR API KEY in the index.js file with your actual API key before running the server
  • The code in GitHub has been modularized to enhance reusability. You have the option to utilize any of the references as per your requirement.
const express = require('express');
const chargebee = require('chargebee');
const path = require('path');
const fetch = require('node-fetch');

// CORS is enabled only for demo. Please dont use this in production unless you know about CORS
const cors = require('cors');
const siteName = 'SITE_ID'; // Add your site name here
const API_KEY = 'API_KEY'; // Add your Full access API key here

  site: siteName, // Enter your Side ID here
  api_key: API_KEY // Enter your publishable API key here
const app = express();


// Configure your static file paths here. Images, CSS and JS files should be inside this path
app.use('/static', express.static('/'));

  Fetch Item API
  request params - Product ID, Item type - plan/charge
app.get('/api/fetch-items', (req, res) => {
      limit: 1,
      'status[is]': 'active',
      'product_id[is]': req.query.product_id,
      'type[is]': req.query.type
    .request(function (error, result) {
      if (error) {
        //handle error
      } else {
        var item;
        if (result.list.length) {
          item = result.list[0].item;

  Fetch Item Price API
  request params - Item ID
app.get('/api/fetch-item-prices', (req, res) => {
  let price = {};
      limit: 100,
      'item_id[is]': req.query.item_id
    .request(function (error, result) {
      if (error) {
        //handle error
      } else {
        for (var i = 0; i < result.list.length; i++) {
          var entry = result.list[i];
          var item = entry.item_price;
          if (price[item.variant_id]) {
          } else {
            price[item.variant_id] = [item];

  Fetch Variants API
  request params - Product ID
app.get('/api/variants', async (req, res) => {
  let variants = [];
  const response = await fetch(
      headers: {
        Authorization: `Basic ${Buffer.from(API_KEY).toString('base64')}`
  const varJson = await response.json();
  for (let i = 0; i < varJson.list.length; i++) {
    const variant = varJson.list[i].variant;
  res.status(200).json({ list: variants });

  Fetch Checkout Link
  request params - Item Price ID, Customer ID (optional)
*/'/api/generate_checkout_new_url', async (req, res) => {
  try {
      .request(function (error, result) {
        if (error) {
          //handle error
        } else {
  } catch (e) {

// Configure the path of your HTML file to be loaded
app.get('/', (req, res) => {
    path.join(__dirname, '/cb-widget.html')

app.listen(8000, () =>
  console.log('subscription-enrollment-example listening on port 8000!')

Step 6: Start local server

Run node index.js in your terminal to start the server.

Step 7: Check your widget

Access the widget at http://localhost:8000.

Was this tutorial helpful ?
Need more help?

We're always happy to help you with any questions you might have!

In this Page