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

# Subscription Management

> Manage subscription lifecycle events with Flowglad's SDKs

## Overview

Use Flowglad to create, update, and cancel [subscriptions](/features/subscriptions) across web and server experiences.

## How to use

### Create Subscriptions

Subscriptions are most commonly created as part of the standard checkout flow with `createCheckoutSession` when the price type is `subscription`. The checkout session will collect payment details from the customer, even if the subscription includes a trial period, so a charge attempt will be made automatically when the customer's trial expires.

#### Free Trials

You can create subscriptions with free trials with or without requiring a valid payment method up-front.

If you prefer to start customers on free trials without asking for payment details, you can use flowgladServer's `createSubscription` method, passing in the `priceSlug` and other optional fields (see the create subscription API [body](/api-reference/subscriptions/create-subscription#body-customer-id) for more details on the parameters).

<Note>
  You do not need to include `customerId` when calling `createSubscription` with the `flowgladServer`, as the server client is already bound to the requesting customer scope.
</Note>

If you create a subscription without a payment method attached, you must collect a payment method from the customer before the trial period ends in order to activate the subscription. You can do this via the `createActivateSubscriptionCheckoutSession` method.

If there is a payment method associated with a subscription on a free trial, Flowglad will attempt to charge the payment method when the trial ends. If the payment method fails, Flowglad will not activate the subscription.

### Cancel Subscriptions

You can cancel a subscription with `useBilling`'s `cancelSubscription` from client side or with `flowgladServer`'s `cancelSubscription` from server side. You provide the subscription `id` and specify the cancellation timing-- either `immediately` or `at_end_of_current_billing_period` (see the cancel subscription API [body](/api-reference/subscriptions/cancel-subscription#body-cancellation) for details).

### Uncancel Subscriptions

If a subscription has been scheduled for cancellation (status `cancellation_scheduled`), you can reverse the cancellation before it takes effect using `useBilling`'s `uncancelSubscription` from client side or `flowgladServer`'s `uncancelSubscription` from server side. You provide just the subscription `id`.

**Requirements:**

* The subscription must be in `cancellation_scheduled` status (cancellation timing was `at_end_of_current_billing_period`)
* For paid subscriptions, a valid payment method must exist for the subscription. For free subscriptions, no payment method is required

**Behavior:**

* The operation is idempotent - calling it on a subscription that isn't scheduled for cancellation will silently succeed without uncanceling
* The subscription status reverts to `active` (or `trialing` if still in trial period)
* Any billing runs that were aborted due to the cancellation are rescheduled

### Adjust Subscriptions

You can upgrade or downgrade a subscription using `useBilling`'s `adjustSubscription` from client side or `flowgladServer`'s `adjustSubscription` from server side. This allows customers to change their plan mid-billing cycle with automatic proration handling.

**Input Options:**

There are three ways to specify the new plan:

* `priceSlug`: Reference a price by its slug (simplest form)
* `priceId`: Reference a price by its ID
* `subscriptionItems`: Array of subscription items for complex multi-item adjustments

**Timing Options:**

The `timing` parameter controls when the adjustment takes effect:

* **`auto`** (default): Automatically determines timing based on whether this is an upgrade or downgrade. Upgrades happen immediately with proration; downgrades happen at the end of the current billing period.
* **`immediately`**: Apply the change immediately. For upgrades, the customer is charged the prorated difference. For downgrades, the plan changes immediately but no refund is issued.
* **`at_end_of_current_billing_period`**: Apply the change at the end of the current billing period (only valid for downgrades).

**How `auto` determines upgrade vs downgrade:**

The `auto` timing compares the total monetary value of the current subscription items against the new subscription items:

* **Upgrade:** New total price > current total price → applies immediately
* **Downgrade:** New total price \< current total price → applies at end of period
* **Same price:** New total price = current total price → applies immediately

The total price is calculated as the sum of `unitPrice × quantity` across all subscription items. This means switching between plans at the same price point (e.g., changing from "Pro Monthly" to "Pro Annual" with equivalent monthly cost) is treated as a same-price change and applies immediately.

**Proration:**

By default, immediate adjustments are prorated (`prorate: true`). This means:

* **Upgrades**: Customer is charged the prorated difference for the remainder of the billing period
* **Downgrades**: No refund is issued; the plan changes take effect based on timing

Set `prorate: false` to skip the mid-period proration charge. The plan change still happens immediately, but the customer isn't charged until the next billing period. This effectively gives the customer the upgraded features for free until the period ends.

<Note>
  The `prorate` option only applies to `immediately` and `auto` timing. The `at_end_of_period` timing has no proration since changes take effect at the next billing cycle.
</Note>

**Subscription ID Resolution:**

If the customer has exactly one active subscription, the `subscriptionId` is automatically resolved. If the customer has multiple subscriptions, you must specify which subscription to adjust using the `subscriptionId` parameter.

**Requirements:**

* The subscription must be active (not in a terminal state like `canceled`)
* The subscription must be a renewing subscription (not a one-time purchase)
* The subscription cannot be on a free plan (use `createSubscription` to upgrade from free instead)
* A valid payment method is required for proration charges (upgrades with `prorate: true`)
* The new price must belong to the same pricing model as the subscription and be an active, recurring price

<Warning>
  For subscriptions on a free trial without a payment method, proration upgrades will fail. Either collect a payment method first using `createAddPaymentMethodCheckoutSession`, or use `prorate: false` to skip the immediate charge.
</Warning>

**Usage-Based Pricing:**

Subscription adjustments only affect recurring (subscription-type) prices. Usage-based charges are **not** prorated during adjustments—they continue to accumulate and are billed at the end of the billing period based on actual consumption, regardless of any plan changes.

**Resource-Based Pricing (Seats, API Keys, etc.):**

When adjusting subscriptions that include [resources](/features/resources) like seats or API keys, Flowglad validates that active claims fit within the new capacity:

* **Upgrades:** Claims are preserved, and new capacity is immediately available
* **Downgrades:** If claimed resources exceed the new capacity, the adjustment is blocked

For downgrades that would exceed capacity, you must first release excess resources:

```typescript lib/downgrade-with-seats.ts theme={null}
import { flowglad } from './flowglad'

export async function downgradeWithResourceCheck(params: {
  customerExternalId: string
  newPriceSlug: string
}) {
  const server = flowglad(params.customerExternalId)

  // Check current seat usage before downgrading
  const { usage } = await server.getResourceUsage({ resourceSlug: 'seats' })

  // The new plan's capacity - in production, fetch this from your pricing config
  const newCapacity = 5

  if (usage.claimed > newCapacity) {
    // Must release excess claims first
    throw new Error(
      `Cannot downgrade: ${usage.claimed} seats claimed, new plan only includes ${newCapacity}.`
    )
  }

  // Safe to proceed with downgrade
  return server.adjustSubscription({
    priceSlug: params.newPriceSlug
  })
}
```

See [Resources](/features/resources#adjusting-subscriptions-with-resources) for detailed examples of handling seat-aware plan changes.

### What you can do

* Allow customer to complete a subscription product checkout which will create a subscription automatically.
* Create subscriptions server-side using the Flowglad Server SDK.
* Trigger cancellations and uncancellations from server or client flows.
* Adjust existing subscriptions to upgrade or downgrade plans with automatic proration.
* Check entitlements for feature access or usage credits based on customer subscription.

### Example: Create subscription

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function StartTrialButton({
      priceId,
      successUrl,
      cancelUrl,
    }: {
      priceId: string
      successUrl: string
      cancelUrl: string
    }) {
      const { createCheckoutSession } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleClick = async () => {
        setError(null)

        if (!createCheckoutSession) {
          setError('Checkout is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await createCheckoutSession({
            priceId,
            successUrl,
            cancelUrl,
            autoRedirect: true,
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to start the checkout session.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleClick} disabled={isLoading}>
            {isLoading ? 'Redirecting…' : 'Start Trial'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'

    const flowglad = (customerExternalId: string) => {
      // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    export async function createSubscription(customerExternalId: string) {
      // Pass YOUR app's user/organization ID, not Flowglad's customer ID
      const response = await flowglad(customerExternalId).createSubscription({
        priceSlug: 'pro_plan',
        quantity: 1,
        metadata: {
          plan: 'pro',
        },
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>

### Example: Cancel subscription

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function CancelSubscriptionButton({
      subscriptionId,
    }: {
      subscriptionId: string
    }) {
      const { cancelSubscription } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleCancel = async () => {
        setError(null)

        if (!cancelSubscription) {
          setError('Cancellation is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await cancelSubscription({
            id: subscriptionId,
            cancellation: {
              timing: 'at_end_of_current_billing_period',
            },
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to cancel subscription.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleCancel} disabled={isLoading}>
            {isLoading ? 'Cancelling…' : 'Cancel Subscription'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'
    import { auth } from '@/lib/auth'
    /**
    * Note: this only demonstrates how to 
    * initiate subscription cancellation from your backend.
    * If you initiate the cancellation using `cancelSubscription`
    * from your frontend, your Flowglad route handler
    * will handle this automatically
    */
    const flowglad = (customerExternalId: string) => {
      // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    export async function cancelSubscription(subscriptionId: string, customerExternalId: string) {
      // Pass YOUR app's user/organization ID, not Flowglad's customer ID
      const response = await flowglad(customerExternalId).cancelSubscription({
        id: subscriptionId,
        cancellation: {
          timing: 'at_end_of_current_billing_period',
        },
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>

### Example: Uncancel subscription

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function UncancelSubscriptionButton({
      subscriptionId,
    }: {
      subscriptionId: string
    }) {
      const { uncancelSubscription } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleUncancel = async () => {
        setError(null)

        if (!uncancelSubscription) {
          setError('Uncancel is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await uncancelSubscription({
            id: subscriptionId,
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to uncancel subscription.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleUncancel} disabled={isLoading}>
            {isLoading ? 'Uncanceling...' : 'Keep My Subscription'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'
    import { auth } from '@/lib/auth'
    /**
    * Note: this only demonstrates how to 
    * uncancel a subscription from your backend.
    * If you initiate the uncancel using `uncancelSubscription`
    * from your frontend, your Flowglad route handler
    * will handle this automatically
    */
    const flowglad = (customerExternalId: string) => {
      // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    export async function uncancelSubscription(subscriptionId: string, customerExternalId: string) {
      // Pass YOUR app's user/organization ID, not Flowglad's customer ID
      const response = await flowglad(customerExternalId).uncancelSubscription({
        id: subscriptionId,
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>

### Example: Adjust subscription (upgrade)

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function UpgradePlanButton({
      newPriceSlug,
    }: {
      newPriceSlug: string
    }) {
      const { adjustSubscription } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleUpgrade = async () => {
        setError(null)

        if (!adjustSubscription) {
          setError('Plan adjustment is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await adjustSubscription({
            priceSlug: newPriceSlug,
            // timing defaults to 'auto' - upgrades apply immediately
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to upgrade subscription.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleUpgrade} disabled={isLoading}>
            {isLoading ? 'Upgrading…' : 'Upgrade Plan'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'
    /**
    * Note: this only demonstrates how to
    * adjust a subscription from your backend.
    * If you initiate the adjustment using `adjustSubscription`
    * from your frontend, your Flowglad route handler
    * will handle this automatically
    */
    const flowglad = (customerExternalId: string) => {
      // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    export async function upgradeSubscription(customerExternalId: string, newPriceSlug: string) {
      // Pass YOUR app's user/organization ID, not Flowglad's customer ID
      const response = await flowglad(customerExternalId).adjustSubscription({
        priceSlug: newPriceSlug,
        // timing defaults to 'auto' - upgrades apply immediately with proration
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>

### Example: Adjust subscription (downgrade)

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function DowngradePlanButton({
      newPriceSlug,
    }: {
      newPriceSlug: string
    }) {
      const { adjustSubscription } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleDowngrade = async () => {
        setError(null)

        if (!adjustSubscription) {
          setError('Plan adjustment is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await adjustSubscription({
            priceSlug: newPriceSlug,
            // timing defaults to 'auto' - downgrades apply at end of billing period
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to downgrade subscription.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleDowngrade} disabled={isLoading}>
            {isLoading ? 'Processing…' : 'Downgrade Plan'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'

    const flowglad = (customerExternalId: string) => {
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    export async function downgradeSubscription(customerExternalId: string, newPriceSlug: string) {
      const response = await flowglad(customerExternalId).adjustSubscription({
        priceSlug: newPriceSlug,
        // timing defaults to 'auto' - downgrades apply at end of billing period
        // No refund is issued; customer keeps current plan features until period ends
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>

### Example: Adjust subscription with explicit timing

<Tabs>
  <Tab title="Client">
    ```tsx theme={null}
    'use client'

    import { useState } from 'react'
    import { useBilling } from '@flowglad/nextjs'

    export function ImmediateUpgradeButton({
      newPriceSlug,
    }: {
      newPriceSlug: string
    }) {
      const { adjustSubscription } = useBilling()
      const [isLoading, setIsLoading] = useState(false)
      const [error, setError] = useState<string | null>(null)

      const handleUpgrade = async () => {
        setError(null)

        if (!adjustSubscription) {
          setError('Plan adjustment is not available right now.')
          return
        }

        try {
          setIsLoading(true)
          await adjustSubscription({
            priceSlug: newPriceSlug,
            timing: 'immediately', // Force immediate application with proration
          })
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : 'Failed to upgrade subscription.'
          )
        } finally {
          setIsLoading(false)
        }
      }

      return (
        <>
          <button onClick={handleUpgrade} disabled={isLoading}>
            {isLoading ? 'Upgrading…' : 'Upgrade Now'}
          </button>
          {error && <p className="text-sm text-destructive">{error}</p>}
        </>
      )
    }
    ```
  </Tab>

  <Tab title="Server">
    ```ts theme={null}
    import { FlowgladServer } from '@flowglad/server'

    const flowglad = (customerExternalId: string) => {
      return new FlowgladServer({
        customerExternalId,
        getCustomerDetails: async (externalId) => {
          const user = await db.users.findOne({ id: externalId })
          return {
            email: user.email,
            name: user.name,
          }
        },
      })
    }

    // Upgrade with explicit immediate timing and proration
    export async function immediateUpgrade(customerExternalId: string, newPriceSlug: string) {
      const response = await flowglad(customerExternalId).adjustSubscription({
        priceSlug: newPriceSlug,
        timing: 'immediately',
        prorate: true, // Charge prorated difference (this is the default)
      })

      return response.subscription
    }

    // Upgrade immediately without mid-period charge
    export async function immediateUpgradeNoProration(customerExternalId: string, newPriceSlug: string) {
      const response = await flowglad(customerExternalId).adjustSubscription({
        priceSlug: newPriceSlug,
        timing: 'immediately',
        prorate: false, // Skip mid-period charge; new price charged at next billing period
      })

      return response.subscription
    }
    ```
  </Tab>
</Tabs>
