Skip to content

Shopify Dude Fix

Shopify Subscription Widget Defaults to Subscribe & Save? Make One-Time Purchase the First Choice

Put one-time purchase first and selected in a Shopify subscription widget without submitting the wrong selling plan, showing the wrong price, or breaking variant changes.

Type: Fix / Developer Guide · Stage: Optimize · Topic: Subscriptions / Selling Plans / Product Forms

Quick answer

Change the purchase-option component’s selection state, not just its visual order. A one-time purchase should be the first and initially selected option when the selected variant permits one-time buying. In the product form, the selling_plan field must be empty or omitted for that choice. When a customer explicitly chooses a subscription, submit the selected plan’s ID. Then update the displayed price and confirm the cart contains the intended purchase type.

Start with the subscription app’s widget settings. Only change Liquid or JavaScript when the app does not expose the required order/default controls or when you own the custom widget. Shopify’s subscription UX guidance recommends selecting the one-time option on first load where available; it is not a rule that overrides a subscription-only product.

On this page

Why moving the cards with CSS is not a fix

Most subscription product pages contain three separate layers: a visible purchase-option selector, a hidden or generated selling_plan input, and a price display. Moving the one-time card above Subscribe & Save with order: -1 only changes the first layer. A checked subscription radio button or a hidden selling-plan ID can remain active underneath.

The result is a familiar production bug: the page looks like a one-time purchase, the main price still shows the discounted subscription amount, and the cart receives a subscription. Or the reverse happens: a subscription appears selected, but the cart contains a one-time line at the regular price.

Older theme snippets sometimes hard-code the first selling plan in the product form, or use product.selected_or_first_available_selling_plan_allocation as if it proves the shopper chose a subscription. That property can return the first available allocation even when no plan was selected. Use product.selected_selling_plan to distinguish an explicit URL-selected plan, and maintain the widget’s own selection state for subsequent interaction.

Fix the actual source of the default

  1. Check the product first. In Products → [product] → Purchase options, confirm that the product allows one-time purchases. If Only sell this product as a subscription is enabled, do not manufacture a one-time option in the theme.
  2. Find who owns the widget. Inspect the product template in the theme editor. An app block should be configured through its app settings if possible. A custom Liquid snippet can be changed in the theme; a widget rendered by third-party JavaScript may reset DOM changes after initialization.
  3. Change the source order and checked state. Render one-time first and mark it selected for variants that do not require a plan. Clear any previously injected plan ID on initial load, unless the buyer deliberately arrived with a valid selling_plan URL parameter that your design supports.
  4. Synchronize every product form. Themes can have a main form, sticky add-to-cart form, and quick-buy form. A widget that updates one but not the others can submit inconsistent purchase types.
  5. Recalculate prices from the selected variant and plan. A subscription’s selling_plan_allocation.price can differ from the one-time variant price. Never calculate the savings by subtracting an assumed percentage from a hard-coded price.

A small, reliable form-state example

This example is deliberately scoped to a custom widget. It assumes a product form with radio buttons for purchase type and a hidden plan input. Integrate it into your existing variant-change handler rather than installing a second, competing widget script.

<form method="post" action="/cart/add" data-purchase-form>
  <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
  <label>
    <input type="radio" name="purchase_type" value="one_time"
      {% unless product.selected_or_first_available_variant.requires_selling_plan %}checked{% endunless %}
      {% if product.selected_or_first_available_variant.requires_selling_plan %}disabled{% endif %}>
    One-time purchase
  </label>
  <label>
    <input type="radio" name="purchase_type" value="subscription"
      {% if product.selected_or_first_available_variant.requires_selling_plan %}checked{% endif %}>
    Subscribe &amp; Save
  </label>
  <select data-plan-options aria-label="Subscription frequency">
    {% for group in product.selling_plan_groups %}
      {% for plan in group.selling_plans %}
        <option value="{{ plan.id }}">{{ plan.name | escape }}</option>
      {% endfor %}
    {% endfor %}
  </select>
  <input type="hidden" name="selling_plan" value="" data-selling-plan>
  <button type="submit">Add to cart</button>
</form>

Important: The markup illustrates state wiring, not a complete production subscription picker. Real widgets must filter plans by the selected variant’s selling_plan_allocations, handle sold-out and subscription-only variants, and integrate with the theme’s existing form. In a multi-form theme, use unique IDs and properly grouped controls for each form.

document.querySelectorAll('[data-purchase-form]').forEach((form) => {
  const hidden = form.querySelector('[data-selling-plan]');
  const planSelect = form.querySelector('[data-plan-options]');
  const radios = form.querySelectorAll('[name="purchase_type"]');

  function syncPurchaseType() {
    const selected = form.querySelector('[name="purchase_type"]:checked');
    const subscription = selected?.value === 'subscription';
    hidden.value = subscription ? planSelect.value : '';
    planSelect.disabled = !subscription;
    // Call the theme's price update function here, using the selected allocation.
  }

  radios.forEach((radio) => radio.addEventListener('change', syncPurchaseType));
  planSelect.addEventListener('change', syncPurchaseType);
  syncPurchaseType();
});

On a live storefront, preserve explicit deep-link plan selection when it is valid, avoid selecting a plan not offered by the current variant, and make sure the same logic runs when the variant changes. If your theme submits with Ajax, Shopify’s Cart API accepts a selling_plan ID for subscription lines; omit that property for one-time lines.

Keep the information underneath visible

Benefits such as shipping information, ingredient facts, or guarantee bullets are often accidentally nested inside the subscription-only panel. If those bullets apply to both purchase types, move them into a sibling container outside the conditional subscription details. Leave plan-specific disclosures—billing frequency, recurring charges, cancellation terms—associated with the subscription choice. The distinction matters: always visible should not mean misleadingly presented as a one-time benefit.

Common misunderstanding

“The first card is automatically the selected card.” Visual order and form state are independent. “Empty selling_plan and missing selling_plan are the same as a subscription.” No: a one-time cart line has no selected selling plan. “The product allows one-time purchases, so every variant does.” Check the current variant’s requires_selling_plan and eligible allocations; product-level flags can conceal mixed variant eligibility.

How to test this

  1. Open a product that allows both purchase types in a fresh private window. Confirm one-time appears first, is selected, and shows the correct regular price.
  2. Inspect the actual product form before submission: selling_plan must be empty or omitted for one-time, and a valid ID for the selected subscription.
  3. Add a one-time line and inspect /{locale}/cart.js. Confirm its selling_plan_allocation is absent or null; confirm the cart price and checkout line.
  4. Choose each subscription frequency, add it, and verify the cart line contains the matching allocation, price, and recurring terms.
  5. Switch variants after selecting a plan. Confirm unavailable plans are removed, the default is valid for the new variant, and the sticky form agrees with the main form.
  6. Repeat on a subscription-only variant, in mobile quick-buy, and in each market/currency. Verify shared benefit bullets remain visible without concealing subscription disclosures.
  7. Test a deep link with ?selling_plan= and a valid plan ID. Decide explicitly whether the URL choice should override the normal first-load default.

Sources and further reading

Documentation checked October 25, 2026.