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

# Subscriptions

> List, read, create, and change the current customer's subscriptions.

import LearnHooksTip from '/snippets/learn-hooks-tip.mdx';

Hooks for reading and mutating the subscriptions of the customer in [`<UnitPayProvider>`](/react/provider) context — list them, resolve the live one, create a new subscription, change plans, attach addons, and cancel or resume.

<LearnHooksTip />

Every hook returns loading/error state — reads via `isLoading: boolean`, mutations via `isPending: boolean` — plus `error: Error | null`. See [Introduction](/react/introduction). The sections below list each hook's specific returns.

The four mutation hooks that move money (`useCreateSubscription`, `useChangePlan`, `useAttachAddon`) resolve to a [`SettleOutcome`](/react/settlement) — the server decides whether to charge inline, mint an inline `<PaymentForm>`, send an invoice, or take no action — which you handle via the `on*` settlement callbacks in `options`. `useCancelSubscription` and `useUncancelSubscription` move no money, so they return a plain `SubscriptionMutationResult` instead.

## useSubscriptions

Lists every subscription belonging to the customer — across every product, plan, and status — as one cached list. Takes no arguments; the customer comes from `<UnitPayProvider>` context.

**Returns** — `subscriptions: Subscription[]` — every subscription on the customer (up to 50), or `[]` while loading or when none exist.

```tsx theme={null}
import { useSubscriptions } from '@unitpay/react';

export default function SubscriptionList() {
  const { subscriptions, isLoading, error } = useSubscriptions();

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Failed to load subscriptions.</p>;
  if (subscriptions.length === 0) return <p>No subscriptions yet.</p>;

  return (
    <ul>
      {subscriptions.map((sub) => (
        <li key={sub.id}>{sub.planId} — {sub.status}</li>
      ))}
    </ul>
  );
}
```

## useActiveSubscription

Answers "what is the customer billing on right now?" It filters `useSubscriptions` (no extra request) to live statuses — `active`, `trialing`, `pending`, `past_due` — returning both the first match for single-sub portals and the full filtered list for multi-sub customers.

**Parameters**

<ParamField body="planIds" type="readonly string[]">
  Restrict to subscriptions whose `planId` is in this list. Use when the portal is product-scoped — pass the product's plan ids (e.g. from `usePricing(productId)`). Omit to consider every plan the customer has.
</ParamField>

**Returns**

* `subscription` — `Subscription | null` — the first live subscription matching the filter, or `null` while loading or when none are live.
* `subscriptions` — `Subscription[]` — every live subscription matching the filter. Use this when a customer legitimately has more than one (different products, currencies, addons).

```tsx theme={null}
import { useActiveSubscription } from '@unitpay/react';

export default function CurrentPlan() {
  const { subscription, isLoading } = useActiveSubscription();

  if (isLoading) return <p>Loading…</p>;
  if (!subscription) return <p>No active subscription.</p>;

  return (
    <p>
      On {subscription.planId} ({subscription.status}) until{' '}
      {subscription.currentBillingPeriodEnd}
    </p>
  );
}
```

Scope to one product by passing its plan ids:

```tsx theme={null}
import { useActiveSubscription, usePricing } from '@unitpay/react';

export default function ProductPlan({ productId }: { productId: string }) {
  const { pricing } = usePricing(productId);
  const planIds = pricing?.tiers.flatMap((t) => t.plans.map((p) => p.id)) ?? [];
  const { subscription } = useActiveSubscription({ planIds });

  return <p>{subscription ? subscription.planId : 'Not subscribed to this product.'}</p>;
}
```

## useSubscription

A read-only hook that fetches one subscription by id and exposes `previewChange`, a parameterized read that previews what switching to another plan would cost. Mutations live in the dedicated hooks below.

**Parameters**

<ParamField body="subscriptionId" type="string">
  The subscription to fetch. The query stays disabled until an id is provided, so it is safe to pass `undefined` while the id is still loading.
</ParamField>

**Returns**

* `subscription` — `Subscription | null` — the subscription, or `null` while loading or when no id is set.
* `previewChange(newPlanId)` — `(newPlanId: string) => Promise<SubscriptionPreviewChange>` — previews switching to `newPlanId`. Returns the full change breakdown: `direction` (`upgrade` / `downgrade` / `lateral`), `timing` (`immediate` / `deferred`), `dueNow`, `effectiveAt`, entitlement diffs, and credit deltas. Memoized, so it is safe in a `useEffect` dependency array.

```tsx theme={null}
import { useEffect, useState } from 'react';
import { useSubscription } from '@unitpay/react';
import type { SubscriptionPreviewChange } from '@unitpay/react';

export default function PlanSwitcher({ subscriptionId }: { subscriptionId: string }) {
  const { subscription, isLoading, previewChange } = useSubscription(subscriptionId);
  const [preview, setPreview] = useState<SubscriptionPreviewChange | null>(null);

  useEffect(() => {
    previewChange('pln_pro_monthly').then(setPreview);
  }, [previewChange]);

  if (isLoading) return <p>Loading…</p>;
  if (!subscription) return <p>Not found.</p>;

  return (
    <div>
      <p>Current plan: {subscription.planId}</p>
      {preview && (
        <p>Switch is a {preview.direction}; due now {preview.dueNow.amount} {preview.dueNow.currency}.</p>
      )}
    </div>
  );
}
```

## useCreateSubscription

Mutation that subscribes the customer in context to a plan. `salesMotion` defaults to `self_serve` server-side. `useCreateSubscription(options?)` takes no id; `options` is a [`HandleSettlementOptions`](/react/settlement) object of settlement callbacks (`onChargedInline`, `onRequiresForm`, `onInvoiceSent`, `onCreated`, `onDeferred`, `onNoAction`, …).

The returned `createSubscription(input)` alias accepts:

<ParamField body="planId" type="string" required>
  The plan to subscribe to.
</ParamField>

<ParamField body="currency" type="string">
  Override the billing currency. Defaults to the plan's currency.
</ParamField>

<ParamField body="metadata" type="Record<string, unknown>">
  Arbitrary metadata to attach to the subscription.
</ParamField>

<ParamField body="forceForm" type="boolean">
  Force the inline `<PaymentForm>` even when a payment method is already on file.
</ParamField>

<ParamField body="idempotencyKey" type="string">
  Override the auto-generated `Idempotency-Key`.
</ParamField>

**Returns** — a React Query mutation plus `createSubscription(input) => Promise<SettleOutcome>` (an ergonomic alias for `mutateAsync`; the `on*` callbacks have already been dispatched against the outcome).

```tsx theme={null}
import { useState } from 'react';
import { useCreateSubscription } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function Subscribe({ planId }: { planId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { createSubscription, isPending } = useCreateSubscription({
    onChargedInline: () => alert('Subscribed and paid.'),
    onRequiresForm: (r) => setFormClient(r.client),
    onCreated: () => alert('Subscription created (no charge).'),
  });

  return (
    <button onClick={() => createSubscription({ planId })} disabled={isPending}>
      {isPending ? 'Subscribing…' : 'Subscribe'}
    </button>
  );
}
```

## useChangePlan

Mutation that moves an existing subscription to a new plan (upgrade, downgrade, or lateral). The server dispatches inline charge (PLG with a payment method), recovery checkout (no PM or decline), an SLG invoice, or a deferred change. Preview the change first with [`useSubscription`](#usesubscription)'s `previewChange`.

`useChangePlan(subscriptionId, options?)` — the subscription id is the first argument; `options` is a [`HandleSettlementOptions`](/react/settlement) object. The returned `changePlan(input)` alias accepts:

<ParamField body="newPlanId" type="string" required>
  The plan to switch to.
</ParamField>

<ParamField body="forceForm" type="boolean">
  Force the inline `<PaymentForm>` even when a payment method is already on file.
</ParamField>

<ParamField body="idempotencyKey" type="string">
  Override the auto-generated `Idempotency-Key`.
</ParamField>

**Returns** — a React Query mutation plus `changePlan(input) => Promise<SettleOutcome>`.

```tsx theme={null}
import { useState } from 'react';
import { useChangePlan } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function UpgradeButton({ subscriptionId }: { subscriptionId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { changePlan, isPending } = useChangePlan(subscriptionId, {
    onChargedInline: () => alert('Plan upgraded.'),
    onRequiresForm: (r) => setFormClient(r.client),
    onDeferred: (r) => alert(`Scheduled for ${r.scheduledFor}`),
  });

  return (
    <button onClick={() => changePlan({ newPlanId: 'pln_pro' })} disabled={isPending}>
      {isPending ? 'Upgrading…' : 'Upgrade to Pro'}
    </button>
  );
}
```

## useAttachAddon

Mutation that attaches an addon plan to an existing subscription. Portal callers typically set `forceForm` so addon purchases always confirm through the inline `<PaymentForm>` rather than silently debiting the saved card.

`useAttachAddon(subscriptionId, options?)` — the parent subscription id is the first argument; `options` is a [`HandleSettlementOptions`](/react/settlement) object (also supports `onInvoiceAdded`). The returned `attachAddon(input)` alias accepts:

<ParamField body="addonPlanId" type="string" required>
  The addon plan to attach.
</ParamField>

<ParamField body="forceForm" type="boolean">
  Force the dispatcher to mint `requires_form` even when a payment method is on file, so the purchase always shows the inline `<PaymentForm>` and is explicitly confirmed — no silent off-session debit of the default card.
</ParamField>

<ParamField body="idempotencyKey" type="string">
  Override the auto-generated `Idempotency-Key`.
</ParamField>

**Returns** — a React Query mutation plus `attachAddon(input) => Promise<SettleOutcome>`.

```tsx theme={null}
import { useState } from 'react';
import { useAttachAddon } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function AddSeats({ subscriptionId }: { subscriptionId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { attachAddon, isPending } = useAttachAddon(subscriptionId, {
    onChargedInline: () => alert('Addon attached.'),
    onRequiresForm: (r) => setFormClient(r.client),
  });

  return (
    <button
      onClick={() => attachAddon({ addonPlanId: 'pln_extra_seats', forceForm: true })}
      disabled={isPending}
    >
      {isPending ? 'Adding…' : 'Add seats'}
    </button>
  );
}
```

## useCancelSubscription

Mutation that cancels a subscription. Cancellation moves no money, so — unlike the mutations above — it does **not** return a `SettleOutcome`; it resolves to a plain `SubscriptionMutationResult`.

`useCancelSubscription(subscriptionId, options?)` — the subscription id is the first argument. `options` carries a single `onSuccess(result: SubscriptionMutationResult) => void` callback. The returned `cancel(input?)` alias accepts (all optional — call `cancel()` for a default period-end cancel):

<ParamField body="cancelImmediately" type="boolean">
  Cancel right now instead of at the end of the current billing period.
</ParamField>

<ParamField body="cancellationReason" type="string">
  Machine-readable reason code stored on the subscription.
</ParamField>

<ParamField body="cancellationNote" type="string">
  Free-text note explaining the cancellation.
</ParamField>

**Returns** — a React Query mutation plus `cancel(input?) => Promise<SubscriptionMutationResult>`, resolving to `{ subscriptionId, status, cancelAtPeriodEnd, effectiveAt }`.

```tsx theme={null}
import { useCancelSubscription } from '@unitpay/react';

export default function CancelButton({ subscriptionId }: { subscriptionId: string }) {
  const { cancel, isPending } = useCancelSubscription(subscriptionId, {
    onSuccess: (result) =>
      alert(
        result.cancelAtPeriodEnd
          ? `Cancels on ${result.effectiveAt}`
          : 'Canceled immediately.',
      ),
  });

  return (
    <button onClick={() => cancel({ cancellationReason: 'too_expensive' })} disabled={isPending}>
      {isPending ? 'Canceling…' : 'Cancel subscription'}
    </button>
  );
}
```

## useUncancelSubscription

Mutation that reverses a pending cancellation, keeping the subscription live. Like cancel, it moves no money and resolves to a plain `SubscriptionMutationResult`.

`useUncancelSubscription(subscriptionId, options?)` — the subscription id is the first argument. The `uncancel` alias takes no input (call `uncancel()`). `options` carries a single `onSuccess(result: SubscriptionMutationResult) => void` callback.

**Returns** — a React Query mutation plus `uncancel() => Promise<SubscriptionMutationResult>`, resolving to `{ subscriptionId, status, cancelAtPeriodEnd, effectiveAt }`.

```tsx theme={null}
import { useUncancelSubscription } from '@unitpay/react';

export default function ResumeButton({ subscriptionId }: { subscriptionId: string }) {
  const { uncancel, isPending } = useUncancelSubscription(subscriptionId, {
    onSuccess: (result) => alert(`Subscription is ${result.status} again.`),
  });

  return (
    <button onClick={() => uncancel()} disabled={isPending}>
      {isPending ? 'Resuming…' : 'Keep my subscription'}
    </button>
  );
}
```

## See also

<CardGroup cols={2}>
  <Card title="Settlement model" icon="money-bill-transfer" href="/react/settlement">
    The `SettleOutcome` union and `on*` handlers the mutation hooks dispatch.
  </Card>

  <Card title="Invoices" icon="file-invoice" href="/react/invoices">
    Preview renewals with `useUpcomingInvoice` and collect payment.
  </Card>

  <Card title="Entitlements & gates" icon="shield-halved" href="/react/entitlements-and-gates">
    What each subscription grants the customer.
  </Card>

  <Card title="Customer" icon="user" href="/react/customer">
    The customer these subscriptions belong to.
  </Card>
</CardGroup>
