> ## Documentation Index
> Fetch the complete documentation index at: https://docs.embeddables.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrade to the Embed Code 2.0

> How to install the new embed code and upgrade the Stripe component to match

The Embeddables new Embed Code 2.0 is **now available in beta**.

<Warning>
  The old Embed Code will be **deprecated on February 28th, 2026**.
</Warning>

## The new Embed Code 2.0

### The HEAD script

<Tip>
  This script is always the same, regardless of which Embeddable you're
  embedding.
</Tip>

To get the Embed Code, head to your Embeddable in the [Embeddables Web App](https://app.embeddables.com/), and click on the Embed button in the top-right corner.

### The BODY script

<Tip>This part is unchanged from before.</Tip>

```html theme={null}
<savvy id="YOUR_EMBEDDABLE_ID"></savvy>
```

## Upgrading to the Embed Code 2.0

<Steps>
  <Step title="Grab the new embed code">
    In the [Embeddables Web App](https://app.embeddables.com/), open your Embeddable and click the Embed button in the top-right corner to get the HEAD script.
  </Step>

  <Step title="Replace the old embed code with the new one">
    In your website or web app, look for a script element (likely in the `<HEAD>` of your page) that starts with `<script>const SAVVY_PRELOAD_IDS=...`.

    This is the part to replace with the new script. Make sure that when you're done, the new one is sandwiched inside two `<script>` tags.

    Make sure to do this on a staging URL/environment first, so that you can QA your Stripe changes (described below) on both the old and new embed code.
  </Step>

  <Step title="Make any Stripe changes that you need to">
    See the Stripe section below for a list of things to watch out for and update before
    pushing your changes live.

    <Tip>
      If you're on the Embeddables Pro Plan, your Primary Contact will do this part for you.
    </Tip>
  </Step>

  <Step title="Test your changes thoroughly">
    Run some end-to-end tests, including testing any payments and promo codes, to make sure that everything is working as expected.

    Then push live!
  </Step>
</Steps>

## Upgrading Stripe components

<Note>
  The Embed Code 2.0 comes with the latest version of Stripe's JS SDK, which
  includes [certain breaking changes](https://docs.stripe.com/checkout/elements-with-checkout-sessions-api/changelog) to account for.

  See below for a list of things to watch out for and update before upgrading to the new embed code.
</Note>

The main changes to watch out for are:

1. The default layout for Stripe components has changed from 'Tabs' to 'Accordion'. *(Step 1 below)*
2. The `create_element_options` settings structure has changed. *(Step 2 below)*
3. The `applyPromotionCode` response structure has changed. *(Steps 3-4 below)*

<Steps>
  <Step title="Set any unset Stripe component 'layout' settings to 'Tabs'">
    In Stripe's latest version, the default layout is switching from 'Tabs' to 'Accordion'. Therefore, if you have any Stripe components with the 'layout' setting unset, you'll need to set it to 'Tabs' before upgrading to the Embed Code 2.0.

    1. Select your Stripe component in the Web App editor. In the right sidebar under **Options**, find the **Layout** setting.

    2. Select the **Tabs** option (only if no option is currently selected).
  </Step>

  <Step title="Update create_element_options if applicable">
    <Note>
      This step only applies if you've customized the `create_element_options` settings in your Stripe component.
    </Note>

    Open your Stripe component's JSON by clicking the three-dot icon (**⁝**) in the **Options** tab, then select **Edit JSON**.

    Check if the `create_element_options` key exists. If it contains a `billingDetails` property with multiple keys set to `never`, update `create_element_options.fields.billingDetails` to `never`.
  </Step>

  <Step title="Update your applyPromotionCode function logic to handle both 'success' and 'session' response formats">
    Stripe's latest SDK version has changed the response schema of `applyPromotionCode`: instead of `response.success` it now returns `response.session`, which contains a new object format.

    Make sure your function logic handles both formats, so that you can smoothly upgrade to the new embed code.

    <Tip>
      If you prefer, you can get your changes ready on the latest version of each Embeddable, and then push all of those versions live at the same time that you push the new embed code live. That way, you can avoid the additional logic of handling both old and new formats in your code.

      However, the risk here is that there is a window of time when one is live and not the other, or that you later need to roll back for some reason, so take this approach at your own risk!
    </Tip>

    1. Find all references to `applyPromotionCode` in your custom code.

    2. Replace conditional checks that use `response.type === 'success'` with a structure that supports both formats (*see the next step for details on the new format*):

    ```javascript theme={null}
    if (response.success) {
      // Keep your existing logic here
    } else if (response.session) {
      // Add new logic here (see next step)
    }
    ```

    3. Ensure there are no early `return` statements or `throw` errors that would prevent reaching the `session` branch.

    <Tip>
      Once you've upgrade to the new embed code, and are confident that you won't need to revert to the old one, you can safely simplify the logic to stop handling the old `response.success` format.
    </Tip>
  </Step>

  <Step title="Copy and adapt logic for the new response schema">
    Copy all logic from inside the `response.success` branch into the `response.session` branch.

    Update field accesses to use the new structure:

    * Replace `response.success.total` with `response.session.total`.
    * Access numeric values through `minorUnitsAmount`:
      * `total`: `response.session.total.total.minorUnitsAmount`
      * `subtotal`: `response.session.total.subtotal.minorUnitsAmount`
      * `discount`: `response.session.total.discount.minorUnitsAmount`
      * `appliedBalance`: `response.session.total.appliedBalance.minorUnitsAmount`
      * `shippingRate`: `response.session.total.shippingRate.minorUnitsAmount`
      * `taxExclusive`: `response.session.total.taxExclusive.minorUnitsAmount`
      * `taxInclusive`: `response.session.total.taxInclusive.minorUnitsAmount`

    <Tip>
      For other keys not listed here, refer to the [Stripe Custom Checkout Session object documentation](https://docs.stripe.com/js/custom_checkout/session_object).
    </Tip>
  </Step>

  <Step title="Add error handling">
    You can access errors via `response.error?.message`. See the
    [Stripe applyPromotionCode documentation](https://docs.stripe.com/js/custom_checkout/apply_promotion_code)
    for details.
  </Step>

  <Step title="Test your changes">
    Test the modified logic thoroughly:

    * **With old Stripe component**: Apply a promo code and verify it's reflected in the UI, applied to the amount, and that payment completes successfully.
    * **With updated Stripe component**: Repeat the same tests.
  </Step>
</Steps>

### Example: Before and After

**Before:**

```javascript theme={null}
async function applyPromoCode() {
  try {
    const response = await window.StripeCheckout.applyPromotionCode(
      discountCodeUppercase
    );

    if (response.type === "success") {
      context.setUserData({
        promo_code_message: `Your discount $${(
          response.success.total.discount / 100
        ).toFixed(2)} has been applied`,
        discount_code_applied: discountCodeUppercase,
        subtotal: (response.success.total.subtotal / 100).toFixed(2),
        discount: (response.success.total.discount / 100).toFixed(2),
        total: (response.success.total.total / 100).toFixed(2),
        checking_promo_code: false,
      });
    } else {
      context.setUserData({
        promo_code_message: response.error.message,
        checking_promo_code: false,
      });
    }
  } catch (error) {
    console.error("Error applying promo code:", error);
    context.setUserData({
      promo_code_message: `Error trying to apply the promo code ${userData.discount_code}`,
      checking_promo_code: false,
    });
  }
}
```

**After:**

```javascript theme={null}
async function applyPromoCode() {
  try {
    const response = await window.StripeCheckout.applyPromotionCode(
      discountCodeUppercase
    );

    let subtotal, discount, total, message;

    if (response.success) {
      // Old response structure
      subtotal = (response.success.total.subtotal / 100).toFixed(2);
      discount = (response.success.total.discount / 100).toFixed(2);
      total = (response.success.total.total / 100).toFixed(2);
      message = `Your discount $${discount} has been applied`;
    } else if (response.session) {
      // New response structure
      subtotal = (
        response.session.total.subtotal.minorUnitsAmount / 100
      ).toFixed(2);
      discount = (
        response.session.total.discount.minorUnitsAmount / 100
      ).toFixed(2);
      total = (response.session.total.total.minorUnitsAmount / 100).toFixed(2);
      message = `Your discount $${discount} has been applied`;
    }

    if (response.success || response.session) {
      context.setUserData({
        promo_code_message: message,
        discount_code_applied: discountCodeUppercase,
        subtotal,
        discount,
        total,
        checking_promo_code: false,
      });
    } else {
      context.setUserData({
        promo_code_message:
          response.error?.message || "Unknown error applying promo code",
        checking_promo_code: false,
      });
    }
  } catch (error) {
    console.error("Error applying promo code:", error);
    context.setUserData({
      promo_code_message: `Error trying to apply the promo code ${userData.discount_code}`,
      checking_promo_code: false,
    });
  }
}
```
