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

# Customize the Subscription Options widget

> Tailor Subscription Options widget labels and display values by Shopify variant, selling plan, quantity, language, or an exact combination.

Use JavaScript overrides when the standard Subscription Options block settings do not provide enough control for your storefront. You can customize displayed labels, descriptions, prices, per-item text, savings messages, and other presentation values for specific variants, selling plans, and quantities.

When neither an `overrides` nor a `translations` object is configured, the customization engine remains inactive and the widget keeps its standard behavior.

<Warning>
  JavaScript overrides only change what the Subscription Options widget displays. They do not change product prices, selling plan prices, or discounts in the Shopify cart or checkout. Configure actual prices and discounts in Shopify or Firmhouse, then keep the displayed override values in sync.
</Warning>

## Before you start

This is an advanced theme customization intended for developers. You need the numeric Shopify variant and selling plan IDs that you want to target.

### Add a Custom Liquid block to the product page

1. In Shopify admin, go to **Online Store** > **Themes**.
2. Click **Customize** for your active theme.
3. Open the product detail page (PDP) template where you use the Subscription Options block.
4. In the product information section, click **Add block** and select **Custom Liquid**.
5. Move the Custom Liquid block above the Subscription Options block.
6. Add your JavaScript configuration between `<script>` and `</script>` tags:

```html theme={null}
<script>
  // Add the Subscription Options configuration here.
</script>
```

7. Click **Save**.

The Custom Liquid block must appear before the Subscription Options block so the configuration is available when the widget initializes.

## Add the configuration

The following example defines a fallback, a variant override, a selling plan override, a quantity override, and an exact variant, selling plan, and quantity combination:

```html theme={null}
<script>
  window.firmhouse = window.firmhouse || {};
  window.firmhouse.subscriptionOptions = {
    overrides: {
      default: {
        perUnitText: "each",
        description: "Delivered on your chosen schedule."
      },

      variants: {
        "45123456789012": {
          price: "€12.00"
        }
      },

      sellingPlans: {
        "9876543210": {
          optionName: "Monthly delivery",
          savingsText: "Save €3"
        }
      },

      quantities: {
        "2": {
          perUnitText: "per two-pack"
        }
      },

      variantSellingPlans: {
        "45123456789012": {
          "9876543210": {
            price: "€9.00",
            compareAtPrice: "€12.00",
            perItemPrice: "€9.00 per item",
            description: "Monthly delivery for this product variant.",
            quantities: {
              "2": {
                price: "€16.00",
                compareAtPrice: "€24.00",
                perItemPrice: "€8.00 per item",
                savingsText: "Save €8"
              }
            }
          }
        }
      }
    }
  };
</script>
```

Replace the example IDs with your Shopify IDs. Keep IDs in quotation marks so JavaScript treats them as exact string keys.

For one-time purchases, use `one-time-purchase` as the selling plan key.

## Understand override priority

The widget combines matching values in this order:

1. `default`
2. `variants[variantId]`
3. `sellingPlans[sellingPlanId]`
4. `variantSellingPlans[variantId][sellingPlanId]`
5. `quantities[quantity]`
6. `variants[variantId].quantities[quantity]`
7. `sellingPlans[sellingPlanId].quantities[quantity]`
8. `variantSellingPlans[variantId][sellingPlanId].quantities[quantity]`

More specific layers only need to contain the values they change. Values omitted from a combination continue to use the matching quantity, selling plan, variant, or default value.

As an alternative to the nested `variantSellingPlans` structure, exact combinations can be written as:

```js theme={null}
combinations: {
  "45123456789012:9876543210": {
    price: "€9.00"
  }
}
```

An exact variant, selling plan, and quantity combination can also use the compact form:

```js theme={null}
quantityCombinations: {
  "45123456789012:9876543210:2": {
    price: "€16.00",
    perItemPrice: "€8.00 per item"
  }
}
```

## Select the product quantity input

The widget listens for `input`, `change`, and Shopify theme `quantity-selector:update` events from the product page's quantity input and reapplies matching overrides immediately. It recognizes quantity inputs inside these selectors by default:

```text theme={null}
quantity-input,
quantity-selector,
block-quantity-selector,
.product-form__quantity,
input[name="quantity"]:not([type="hidden"]):not([disabled]),
select[name="quantity"]:not([disabled])
```

If your theme uses different markup, set `quantityInputSelector` beside `overrides`. The selector can target the input itself or a wrapper containing an `input[name="quantity"]` or `select[name="quantity"]`:

```html theme={null}
<script>
  window.firmhouse = window.firmhouse || {};
  window.firmhouse.subscriptionOptions = {
    quantityInputSelector: "#ProductInfo-template input[data-product-quantity]",
    overrides: {
      quantities: {
        "3": {
          descriptionHtml: "<strong>Three-pack:</strong> shipped together."
        }
      }
    }
  };
</script>
```

If no matching quantity input is found, the widget uses quantity `1`.

## Available display values

Each display field has a plain-text key and a corresponding HTML key. Use the following keys in any override layer:

| Plain-text key         | HTML key                   | Purpose                                                                                             |
| ---------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- |
| `optionName`           | `optionNameHtml`           | Selling plan or purchase option label                                                               |
| `optionText`           | `optionTextHtml`           | Complete content of a dropdown option                                                               |
| `cardOptionName`       | `cardOptionNameHtml`       | Subscription heading in the card layout                                                             |
| `description`          | `descriptionHtml`          | Plan description                                                                                    |
| `frequencyDescription` | `frequencyDescriptionHtml` | Billing and delivery frequency description while preserving the checkout price-per-item description |
| `price`                | `priceHtml`                | Displayed current price                                                                             |
| `compareAtPrice`       | `compareAtPriceHtml`       | Displayed comparison price                                                                          |
| `optionPrice`          | `optionPriceHtml`          | Complete price content in pill layouts                                                              |
| `perItemPrice`         | `perItemPriceHtml`         | Checkout price-per-item description                                                                 |
| `perUnitText`          | `perUnitTextHtml`          | Unit content such as `each` or `per box`                                                            |
| `savingsText`          | `savingsTextHtml`          | Displayed savings badge or message                                                                  |
| `discountAmount`       | `discountAmountHtml`       | Alias for the savings message                                                                       |

When both versions of a field are provided, the HTML version takes precedence. The earlier aliases `optionHtml`, `perUnitHtml`, and `savingsHtml` remain supported for existing configurations.

Native dropdown options cannot render formatted HTML consistently. For dropdowns, the widget converts `optionTextHtml` or `optionNameHtml` to plain text. The same HTML values remain formatted in card, pill, description, price, and savings elements.

The `frequency` key is metadata used to associate the option with its delivery frequency rather than rendered content, so it accepts a plain string.

### Customize only the frequency description

Use `frequencyDescription` when you want to replace text such as **Billed and delivered every month** without replacing the checkout price-per-item description. The following placeholders are available:

| Placeholder            | Example value | Description                                                            |
| ---------------------- | ------------- | ---------------------------------------------------------------------- |
| `{billing_frequency}`  | `month`       | Localized billing cadence without words such as **every** or **elke**  |
| `{delivery_frequency}` | `month`       | Localized delivery cadence without words such as **every** or **elke** |

Plural cadences include their count, for example `3 months` or `3 maanden`. Keeping **every**, **elke**, or other surrounding grammar outside the billing and delivery placeholders lets you control the complete sentence in each translation.

```html theme={null}
<script>
  window.firmhouse = window.firmhouse || {};
  window.firmhouse.subscriptionOptions = {
    overrides: {
      default: {
        frequencyDescription: "Billed every {billing_frequency}. Delivered every {delivery_frequency}."
      }
    }
  };
</script>
```

The `default` override applies to every selling plan that renders a frequency description. You can still place the same field under `sellingPlans` when only one selling plan needs different wording.

For example, a monthly option keeps its dynamic checkout price and displays:

```text theme={null}
You will pay £10.00 for each item in checkout.
Billed every month. Delivered every month.
```

To show only the billing cadence, use `frequencyDescription: "Billed every {billing_frequency}."`. For Dutch storefronts, you can provide a translated override such as `frequencyDescription: "Elke {billing_frequency} gefactureerd."`.

Use `frequencyDescriptionHtml` instead when the replacement needs trusted HTML formatting. Placeholder values are escaped before they are inserted into HTML.

This override only applies when the widget has a built-in billing or delivery frequency description to replace. If both `description` and `frequencyDescription` resolve for the same option, `frequencyDescription` takes precedence so the checkout price remains visible.

<Warning>
  HTML overrides are inserted into the storefront page. Only use hard-coded, trusted HTML. Do not insert customer-provided or other untrusted content.
</Warning>

## Add translations

Add a `translations` object beside `overrides`. Each locale accepts the same default, variant, selling plan, quantity, and combination structure:

```html theme={null}
<script>
  window.firmhouse = window.firmhouse || {};
  window.firmhouse.subscriptionOptions = {
    overrides: {
      default: {
        optionName: "Subscribe",
        perUnitText: "each"
      }
    },

    translations: {
      nl: {
        default: {
          optionName: "Abonneren",
          perUnitText: "per stuk"
        },
        sellingPlans: {
          "9876543210": {
            description: "Maandelijkse levering"
          }
        }
      },

      "nl-NL": {
        variantSellingPlans: {
          "45123456789012": {
            "9876543210": {
              price: "€9,00"
            }
          }
        }
      }
    }
  };
</script>
```

The widget determines the active locale from the first available source:

1. `window.firmhouse.subscriptionOptions.locale`
2. `Shopify.locale`
3. The page's `<html lang>` value
4. The customer's browser language

Language-level translations are applied before regional translations. For example, an `nl-NL` storefront uses the base overrides, then `nl`, then `nl-NL`. The normal default-to-quantity-combination priority still applies within those translations.

To force a specific locale for testing, add it to the configuration:

```js theme={null}
window.firmhouse.subscriptionOptions.locale = "nl-NL";
```

## Troubleshooting

### Overrides do not appear

* Confirm the configuration loads before `subscription-options.js`.
* Confirm the variant and selling plan IDs are numeric Shopify IDs without the `gid://shopify/...` prefix.
* Check that the configuration uses `window.firmhouse.subscriptionOptions` with the same capitalization shown in the examples.
* Verify that another theme script does not replace `window.firmhouse` after your configuration runs.
* If a quantity override does not appear, confirm `quantityInputSelector` matches the product's visible quantity input or its wrapper.

### The correct price appears in the widget but not at checkout

This is expected when the configured display value differs from Shopify. JavaScript overrides do not change checkout pricing. Update the product, selling plan, or discount configuration that controls the actual Shopify price.

### A translation does not appear

* Inspect `Shopify.locale` or the page's `<html lang>` value to confirm the active locale.
* Use language keys such as `nl` for shared translations and regional keys such as `nl-NL` only for regional differences.
* Set `window.firmhouse.subscriptionOptions.locale` temporarily to test a locale directly.

## Related articles

* [Custom descriptions for subscription options](/configure/shopify/subscription-options-custom-descriptions)
* [Build a custom subscription widget](/configure/shopify/custom-subscription-widget)
