Client-Side Implementation

This guide covers the client-side JavaScript setup for adding a PayPal button to your checkout, from basic configuration through the PayPal JavaScript SDK v6 integration patterns for one-time, vault, recurring, and other payment flows.

PrerequisitesAnchorIcon

Before you can add a PayPal button, make sure you have a few prerequisites out of the way:

  1. Create, verify, and link your PayPal account in the Braintree Control Panel
  2. Generate a client token on your server
    • See Hello, Server and Hello, Client for a walkthrough of creating and using a client token
    • You will use the client token when you initialize your components

Browser supportAnchorIcon

Learn more about browser support for v3 of our JavaScript SDK.

Complete basic configurationAnchorIcon

If you are using script tags to load files, be sure to at least include:

  1. HTML
<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.146.0/js/client.min.js"></script>
<!-- Load the PayPal Checkout component. -->
<script src="https://js.braintreegateway.com/web/3.146.0/js/paypal-checkout.min.js"></script>

Regardless of which method you use to load files, create a div element where your PayPal button will appear.

  1. HTML
<div id="paypal-button"></div>

Initialize componentsAnchorIcon

Integrating PayPal payments requires a client. After you've created a client, pass it to the PayPal Checkout component to accept your payments.

  1. Async/Await
// Create a client.
async function setupPayPalCheckout() {
  try {
    const clientInstance = await braintree.client.create({
      authorization: CLIENT-AUTHORIZATION
    });

    // Create a PayPal Checkout component.
    const paypalCheckoutInstance = await braintree.paypalCheckout.create({
      client: clientInstance
    });

    // Load the PayPal JS SDK (see Load the PayPal JS SDK section)
  } catch (err) {
    // Handle component creation error
  }
}

The console logs a component creation error if braintree.client.create or braintree.paypalCheckout.create fails.

Load the PayPal JS v6 SDKAnchorIcon

We now support integrating the PayPal JavaScript SDK v6 through the Braintree JavaScript SDK v3 so merchants can add PayPal checkout with a single, high-level integration.

Prerequisites for the v6 integrationAnchorIcon

  • Active Braintree account
  • PayPal REST app with a client ID (sandbox or live) for the merchant or platform
  • HTTPS web origin where you load the Braintree Web SDK and host your checkout
  • A server environment where you already create and capture orders through Braintree's backend APIs (recommended pattern)

Minimum SDK versionsAnchorIcon

SDKVersion
PayPal JavaScript Web SDKv6
@paypal/paypal-js (npm)9.3.0

Complete common setupAnchorIcon

Load the Braintree client SDK and the PayPal Checkout v6 JavaScript library. These scripts are required for all PayPal v6 integrations.

  1. Load
<script src="https://js.braintreegateway.com/web/4.0.0-alpha.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/4.0.0-alpha.0/js/paypal-checkout-v6.min.js"></script>

Automatic environment detectionAnchorIcon

The Braintree PayPal Checkout v6 wrapper automatically loads the correct PayPal SDK based on your Braintree environment configuration:

  • Live environment: Loads the PayPal SDK from paypal.com
  • Sandbox environment: Loads the PayPal SDK from sandbox.paypal.com

This happens automatically when you call paypal.loadPayPalSDK(). No additional configuration is required. The environment is determined by the client token or authorization you provide when creating the Braintree client instance.

Set up a one-time paymentAnchorIcon

Basic checkout for a single purchase.

Define the PayPal trigger button that the integration will use to display transaction status or messages.

  1. HTML
<paypal-button type="pay" id="paypal-one-time-payment-button"></paypal-button>

Initialize through a Braintree client token and create a PayPal Checkout v6 instance that will manage the PayPal JavaScript SDK and checkout flow.

  1. Async/Await
async function setupPayPalCheckoutV6() {
  try {
    const client = await braintree.client.create({
        authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createOneTimePaymentSession({
      amount: "10.00",
      currency: "USD",
      intent: "capture",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-one-time-payment-button")
      .addEventListener("click", function () {
          session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the nonce after the buyer approves the payment.

Set up the vault flow (save a PayPal account)AnchorIcon

Save a PayPal account for future transactions without charging.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Initialize the Braintree client and PayPal Checkout v6, load the PayPal SDK, and start a billing agreement session that vaults the buyer's PayPal account through createBillingAgreementSession and tokenizePayment.

  1. Async/Await
async function setupVaultFlow() {
  try {
    const client = await braintree.client.create({
        authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createBillingAgreementSession({
      billingAgreementDescription: "Save for future payments",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          billingToken: data.billingToken,
        });
        console.log("Vaulted nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-vault-button")
      .addEventListener("click", function() {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the vaulted nonce after the buyer approves the agreement.

Set up a recurring planAnchorIcon

Fixed repeating payments on a schedule, like a monthly subscription.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Create a PayPal billing agreement for a recurring payment type using planType: 'RECURRING', have the buyer approve the agreement, then tokenize the billing agreement to charge the customer on a recurring schedule.

  1. Async/Await
async function setupRecurringPlan() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createBillingAgreementSession({
      billingAgreementDescription: "Monthly recurring subscription",
      planType: "RECURRING",
      amount: "29.99",
      currency: "USD",
      planMetadata: {
        billingCycles: [
          {
            billingFrequency: "1",
            billingFrequencyUnit: "MONTH",
            numberOfExecutions: "12",
            sequence: "1",
            startDate: new Date(Date.now() + 86400000)
              .toISOString()
              .split("T")[0],
            trial: false,
            pricingScheme: {
              pricingModel: "VARIABLE",
              price: "29.99",
            },
          },
        ],
        currencyIsoCode: "USD",
        name: "Monthly Recurring Plan",
        productDescription: "Monthly recurring subscription service",
        productQuantity: "1.0",
        productPrice: "29.99",
        totalAmount: "29.99",
      },
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
        console.log("Recurring nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-recurring-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the recurring nonce after the buyer approves the agreement.

Set up a subscription planAnchorIcon

Similar to recurring but specifically for subscriptions with configurable billing cycles.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Create a PayPal subscription flow using planType: 'SUBSCRIPTION' where the buyer clicks a Subscribe button. Manage and bill the customer on an ongoing schedule from your backend.

  1. Async/Await
async function setupSubscriptionPlan() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createBillingAgreementSession({
      billingAgreementDescription: "Monthly subscription for premium service",
      planType: "SUBSCRIPTION",
      amount: "233.00",
      currency: "USD",
      planMetadata: {
        billingCycles: [
          {
            billingFrequency: "1",
            billingFrequencyUnit: "MONTH",
            numberOfExecutions: "3",
            sequence: "1",
            startDate: new Date(Date.now() + 86400000)
              .toISOString()
              .split("T")[0],
            trial: false,
            pricingScheme: {
              pricingModel: "FIXED",
              price: "200.00",
            },
          },
        ],
        currencyIsoCode: "USD",
        name: "Premium Subscription",
        productDescription: "Premium Service Plan",
        productQuantity: "1.0",
        oneTimeFeeAmount: "10",
        shippingAmount: "3.0",
        productPrice: "200",
        taxAmount: "20",
        totalAmount: "233.00",
      },
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
        console.log("Subscription nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-subscription-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the subscription nonce after the buyer approves the agreement.

Set up an unscheduled planAnchorIcon

Variable or on-demand payments, like pay-as-you-go services.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Create a billing agreement session with planType: 'UNSCHEDULED' for variable or on-demand charges, then tokenize the returned billing token so you can bill the customer whenever usage occurs.

  1. Async/Await
async function setupUnscheduledPlan() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createBillingAgreementSession({
      billingAgreementDescription: "Pay-as-you-go service",
      planType: "UNSCHEDULED",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
        console.log("Unscheduled nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-unscheduled-plan-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the unscheduled nonce after the buyer approves the agreement.

Set up checkout with vaultAnchorIcon

Charge a customer and save their PayPal account in a single flow.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Initialize the Braintree client and PayPal Checkout v6 instance. Create a checkout session that both processes the payment and vaults the buyer's PayPal account for future use, so merchants can charge the customer again without requiring re-approval.

  1. Async/Await
async function setupCheckoutWithVault() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createCheckoutWithVaultSession({
      amount: "10.00",
      currency: "USD",
      intent: "capture",
      billingAgreementDetails: {
        description: "Monthly subscription to Totally Real Products!",
      },
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          orderID: data.orderId,
          payerID: data.payerId,
        });
        console.log("Checkout with vault nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-checkout-with-vault-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the checkout-with-vault nonce after the buyer approves the payment.

Set up checkout with a vaulted payment methodAnchorIcon

Starts a vault-initiated checkout flow using a previously vaulted payment method token. This flow supports one-click repeat purchases without the PayPal SDK buttons.

Initialize the Braintree client and PayPal Checkout v6 instance. Create a checkout session that accepts a vaultInitiatedCheckoutPaymentMethodToken and both retrieves the vaulted payment method and processes the payment.

  1. Async/Await
async function setupVaultInitiatedCheckout() {
  const client = await braintree.client.create({
    authorization: "CLIENT-TOKEN",
  });
  const paypal = await braintree.paypalCheckoutV6.create({ client: client });
  await paypal.loadPayPalSDK();
  try {
    const payload = await paypal.startVaultInitiatedCheckout({
      vaultInitiatedCheckoutPaymentMethodToken: 'pm_abc123_vaulted_paypal_token',
      amount: '10.00',
      currency: 'USD',
      intent: 'capture'
    });

    // Submit payload.nonce to your server
    console.log('Payment nonce:', payload.nonce);
  } catch (err) {
    if (err.code === 'PAYPAL_CHECKOUT_V6_VIC_CANCELED') {
      console.log('Customer closed the popup');
    }
  }
}

The browser console logs the payment nonce after the payment succeeds. A caught error with code PAYPAL_CHECKOUT_V6_VIC_CANCELED indicates the customer closed the popup.

Focus the checkout windowAnchorIcon

Focuses the PayPal window if it was opened using startVaultInitiatedCheckout.

  1. Javascript
paypal.focusVaultInitiatedCheckoutWindow();

Close the checkout windowAnchorIcon

Closes the PayPal window if it was opened using startVaultInitiatedCheckout.

  1. Javascript
paypal.closeVaultInitiatedCheckoutWindow();

Add line items and shipping optionsAnchorIcon

Itemized checkout with shipping and tax breakdown.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Create a one-time payment session that includes detailed line items, including a discount, and multiple shipping options, so the PayPal checkout shows a full price breakdown before the buyer approves.

  1. Async/Await
async function setupLineItemsCheckout() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createOneTimePaymentSession({
      amount: "40.00",
      currency: "USD",
      intent: "capture",
      lineItems: [
        { quantity: "2", unitAmount: "15.00", name: "Widget", kind: "debit" },
        { quantity: "1", unitAmount: "10.00", name: "Gadget", kind: "debit" },
        { quantity: "1", unitAmount: "5.00", name: "Discount", kind: "credit" },
      ],
      shippingOptions: [
        {
          id: "standard",
          label: "Standard (5-7 days)",
          selected: true,
          type: "SHIPPING",
          amount: { currency: "USD", value: "5.00" },
        },
        {
          id: "express",
          label: "Express (2-3 days)",
          selected: false,
          type: "SHIPPING",
          amount: { currency: "USD", value: "12.00" },
        },
      ],
      amountBreakdown: {
        itemTotal: "40.00",
        shipping: "5.00",
        discount: "5.00",
      },
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-one-time-payment-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the nonce after the buyer approves the payment.

Handle dynamic payment updatesAnchorIcon

Update shipping and amount when the customer changes address during checkout.

Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.

Create a one-time payment session that listens for onShippingAddressChange, recalculates shipping based on the buyer's new address, calls updatePayment with the new totals, including an updated amount breakdown, and then tokenizes the approved payment once checkout is complete.

  1. Async/Await
async function setupDynamicPaymentUpdates() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createOneTimePaymentSession({
      amount: "10.00",
      currency: "USD",
      intent: "capture",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
      },
      onShippingAddressChange: function (data) {
        var newShipping =
          data.shippingAddress.city === "Chicago" ? "10.00" : "5.00";
        return paypal.updatePayment({
          paymentId: data.orderId,
          amount: (10 + parseFloat(newShipping)).toFixed(2),
          currency: "USD",
        });
      },
    });
    document
      .getElementById("paypal-one-time-payment-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the nonce after the buyer approves the payment.

In addition to onShippingAddressChange, you can listen for onShippingOptionsChange, which fires when the buyer selects a different shipping option instead of changing their address. The callback receives errors, orderId, and selectedShippingOption, which includes id, label, amount, type, and selected.

  1. onShippingOptionsChange
onShippingOptionsChange: function (data) {
  // data.selectedShippingOption: { id, label, amount: { currency, value }, type, selected }
  return paypal.updatePayment({
    paymentId: data.orderId,
    amount: recalculateTotal(data.selectedShippingOption),
    currency: "USD",
  });
},

Set up Pay Later paymentsAnchorIcon

Enable PayPal Pay Later financing options for eligible customers.

Define the PayPal trigger button that the integration will use to display transaction status or messages.

Initialize using a Braintree client token and create a PayPal Checkout v6 instance, then load the PayPal SDK and check eligibility using findEligibleMethods. If Pay Later is eligible for the transaction, retrieve additional configuration details using getDetails to properly configure the button component. Then use createPayLaterSession to enable Pay Later financing. Eligible customers can then pay over time with deferred payment options.

  1. Async/Await
async function setupPayLater() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();

    // Check if Pay Later is eligible for this transaction
    const eligibility = await paypal.findEligibleMethods({
      amount: "100.00",
      currency: "USD",
      countryCode: "US",
    });

    if (!eligibility.paylater) {
      console.log("Pay Later not eligible for this transaction");
      return;
    }

    // Get additional details for button configuration
    const details = eligibility.getDetails("paylater");
    const button = document.getElementById("paypal-pay-later-button");

    // Configure button with details from eligibility check
    button.setAttribute("countryCode", details.countryCode);
    button.setAttribute("productCode", details.productCode);

    const session = paypal.createPayLaterSession({
      amount: "100.00",
      currency: "USD",
      intent: "capture",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
      },
    });

    button.style.display = "block";
    button.addEventListener("click", function () {
      session.start();
    });
  } catch (err) {
    // Handle error
  }
}

The browser console logs a message if Pay Later isn't eligible for the transaction, and logs the nonce after the buyer approves the payment.

Display PayPal Messages (Pay Later promotional messaging)AnchorIcon

Display PayPal Pay Later, installment, and PayPal Credit promotional messaging near your product or checkout price using createMessages.

  1. HTML
<paypal-message logo-type="inline" text-color="monochrome"></paypal-message>
  1. Async/Await
async function setupPayPalMessages() {
  try {
    await paypal.loadPayPalSDK();

    const messagesInstance = await paypal.createMessages({
      buyerCountry: "US",
      currencyCode: "USD",
    });

    const messageEl = document.querySelector("paypal-message");

    const content = await messagesInstance.fetchContent({
      amount: "99.99",
      onReady: function (content) {
        messageEl.setContent(content);
      },
    });

    // Update later, for example when the cart total changes:
    // content.update({ amount: '149.99' });
  } catch (err) {
    console.error("Error loading PayPal message content:", err);
  }
}

The browser console logs an error if the message content fails to load.

Control PayPal button textAnchorIcon

Control the call-to-action button text in the PayPal checkout window.

The commit parameter controls the text displayed on the call-to-action button in the PayPal member checkout window. By default, commit: true is used, which displays "Pay Now" to indicate immediate payment. When commit: false is used, the button displays "Continue" or similar language, indicating that the buyer will be returned to your site to complete additional steps before finalizing the transaction.

Use commit: false when you have a final checkout or review page that buyers need to visit after PayPal approval but before order completion.

  1. Async/Await
async function setupButtonText() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();

    // Example with commit: false for "Continue" button
    const session = paypal.createOneTimePaymentSession({
      amount: "10.00",
      currency: "USD",
      intent: "capture",
      commit: false, // Shows "Continue" instead of "Pay Now"
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
        // Redirect to final checkout page for review
        window.location.href = "/checkout/review?nonce=" + payload.nonce;
      },
    });
    document
      .getElementById("paypal-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The buyer is redirected to the checkout review page with the nonce appended to the URL after approval.

Handle server-side shipping callbacksAnchorIcon

Handle shipping address and option updates on your backend server.

Use the shippingCallbackUrl parameter to provide a secure HTTPS endpoint that handles shipping calculations on your server instead of client-side JavaScript callbacks. For details on how server-side shipping callbacks work, see Integrating Shipping Module.

  1. Async/Await
async function setupServerSideShipping() {
  try {
    const client = await braintree.client.create({
      authorization: "CLIENT-TOKEN",
    });
    const paypal = await braintree.paypalCheckoutV6.create({ client: client });
    await paypal.loadPayPalSDK();
    const session = paypal.createOneTimePaymentSession({
      amount: "10.00",
      currency: "USD",
      intent: "capture",
      shippingCallbackUrl: "https://yourserver.com/api/paypal/shipping-callback",
      onApprove: async function (data) {
        const payload = await paypal.tokenizePayment({
          payerID: data.payerId,
          orderID: data.orderId,
        });
        console.log("Nonce:", payload.nonce);
      },
    });
    document
      .getElementById("paypal-button")
      .addEventListener("click", function () {
        session.start();
      });
  } catch (err) {
    // Handle error
  }
}

The browser console logs the nonce after the buyer approves the payment.

Additional session optionsAnchorIcon

The following options are available on createOneTimePaymentSession and createPayLaterSession. locale, landingPageType, enableShippingAddress, shippingAddressEditable, and riskCorrelationId are also available on createBillingAgreementSession.

OptionValuesDescription
contactPreferenceNO_CONTACT_INFO (default), RETAIN_CONTACT_INFO, UPDATE_CONTACT_INFOControls whether and how the buyer's contact info is shown and editable on the PayPal page. Required if using shippingAddressOverride with a different recipient. Currently available for US-based merchants only.
localeLocale code, such as en_US or fr_FRCustomizes the PayPal experience language and format.
landingPageTypelogin, billingForces the PayPal landing page to the login screen or the guest checkout/billing screen. Defaults to PayPal's own selection if omitted.
enableShippingAddresstrue or falseWhen true, prompts the customer for a shipping address. Defaults to suppressed when omitted.
shippingAddressEditabletrue or falseControls whether a displayed shipping address is editable. Pass false to make it read-only. Defaults to editable when omitted.
riskCorrelationIdRisk correlation ID for advanced fraud protection. Stored and used during tokenization.
offerCredittrue or falseWhether to offer PayPal Credit.


Next: Choose your integrationAnchorIcon

The rest of your configuration will be determined by how you'd like to use PayPal.

See a detailed comparison of One-Time Payments, Vaulted Payments, and Recurring Payments.