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

# Subscription upgrade block

> The Aftersell Cart Subscription upgrade sub-block: convert eligible one-time lines to a subscription from the cart.

> The **Subscription upgrade** block is a sub-block that nests inside [**Cart items**](/aftersell/cart/cart-items-block). It prompts shoppers to switch an eligible one-time line item to a subscription right from the cart, turning a one-time purchase into recurring revenue at the point of decision, and lets shoppers who are already subscribed change or cancel their plan on that line.

## Behavior

* **Only appears on eligible lines.** The block reads the product's selling plans and renders nothing on a line whose product has no subscription plans.
* **Never offered on reward gifts.** Subscribing to an auto-granted free gift would strip its reward status, so the prompt is suppressed on those lines.
* On a **one-time line**, it shows the upgrade call-to-action.
* On an **already-subscribed line**, it shows a plan selector plus the "downgrade to one-time" option.

## Settings

| Setting              | What it controls                                                                                       | Default                         |
| -------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------- |
| **Button text**      | The upgrade call-to-action on a one-time line. Supports the `{{discount}}` and `{{plan name}}` tokens. | `Subscribe & Save`              |
| **Unsubscribe text** | The option that reverts a subscribed line to a one-time purchase.                                      | `Downgrade - One time purchase` |

## Placement and limits

* **Parent:** nests inside Cart items only.
* **Maximum:** 1 per cart.
* Not added by default. Not locked, so you can remove or hide it.

Because it's a sub-block, it renders per line, positioned above or below the product content depending on where you place it relative to the Product row. See [how sub-blocks position](/aftersell/cart/cart-items-block#sub-blocks-and-how-they-position).

## Custom template

Supports a [custom template](/aftersell/cart/custom-templates) from its Code tab, which replaces this block's built-in markup with your JSX. These are the props it receives.

This block renders one of two states, and `view.state` tells you which. `view` is never `null` inside a custom template: when a line has no plans, the block renders nothing and your template isn't called.

| Prop              | Type                               | What it's for                                                                                                                         |
| ----------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `view`            | `object`                           | The line's state. Either `{ state: 'upgrade', buttonText, planId }` or `{ state: 'subscribed', plans, activePlanId }`.                |
| `title`           | `string`                           | The **product's** title, not a heading for the block. The built-in template uses it only to build the plan picker's accessible label. |
| `unsubscribeText` | `string`                           | Label for the downgrade-to-one-time option.                                                                                           |
| `busy`            | `boolean`                          | `true` while a plan change is in flight.                                                                                              |
| `selectPlan`      | `(planId: number \| null) => void` | Subscribes or switches plan. Pass `null` to unsubscribe.                                                                              |
| `onChange`        | `(event: Event) => void`           | Ready-made `onChange` for a `<select>`, so you don't have to parse the value yourself.                                                |
| `oneTimeValue`    | `string`                           | The sentinel `<option>` value representing "one-time purchase".                                                                       |
| `line`            | `AftersellCartLine`                | The [cart line](/aftersell/cart/sdk-cart-object#cart-lines) this block belongs to.                                                    |
| `productId`       | `number`                           | Shopify product ID.                                                                                                                   |
| `variantId`       | `number`                           | Shopify variant ID.                                                                                                                   |

Each entry in `view.plans` is `{ id: number, name: string, discountPercent: number }`.

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomTemplate(props) {
  const { view } = props;

  if (view.state === 'upgrade') {
    return (
      <button type="button" onClick={() => props.selectPlan(view.planId)} disabled={props.busy}>
        {view.buttonText}
      </button>
    );
  }

  return (
    <div>
      <select
        aria-label={`Subscription plan — ${props.title}`}
        value={String(view.activePlanId)}
        onChange={props.onChange}
        disabled={props.busy}
      >
        {view.plans.map((plan) => (
          <option key={plan.id} value={String(plan.id)}>
            {plan.name}{plan.discountPercent > 0 ? ` (${plan.discountPercent}% off)` : ''}
          </option>
        ))}
        <option value={props.oneTimeValue}>{props.unsubscribeText}</option>
      </select>
    </div>
  );
}
```

<Note>
  Use `onChange` for a `<select>` and `selectPlan` for buttons. `onChange` already handles the `oneTimeValue` sentinel; if you wire your own handler to a `<select>`, you have to compare against `oneTimeValue` and call `selectPlan(null)` yourself.
</Note>

## Design

Style this block with its **Design** section in the settings panel. These are per-block overrides that layer on top of your global design and fall back to it when blank.

What are design settings? Learn more here: [Design settings](/aftersell/cart/design-settings).
