> ## 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.

# @flowglad/react

> React components and hooks for Flowglad billing and subscription management

<Info>
  NPM Package: [@flowglad/react](https://www.npmjs.com/package/@flowglad/react)
</Info>

## Overview

The React SDK provides components, hooks, and context for managing billing and subscriptions in your React applications. It works with any React framework and requires a backend server running the Flowglad Server SDK.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @flowglad/react @flowglad/server
  ```

  ```bash yarn theme={null}
  yarn add @flowglad/react @flowglad/server
  ```

  ```bash bun theme={null}
  bun add @flowglad/react @flowglad/server
  ```
</CodeGroup>

<Note>
  **Note:** This package requires `@flowglad/server` to be set up on your backend. See the Server tab or [Server SDK documentation](/sdks/server) for setup instructions.
</Note>

## Quick Start

### 1. Wrap Your App with FlowgladProvider

```tsx theme={null}
import { FlowgladProvider } from '@flowglad/react'

export default function App({ children }) {
  return (
    <FlowgladProvider
      requestConfig={{
        headers: {
          // Add custom headers if needed
        },
      }}
    >
      {children}
    </FlowgladProvider>
  )
}
```

### 2. Use the useBilling Hook

```tsx theme={null}
import { useBilling } from '@flowglad/react'

export default function BillingPage() {
  const { checkFeatureAccess, customer, paymentMethods } = useBilling()

  if (!checkFeatureAccess) {
    return <div>Loading...</div>
  }

  if (checkFeatureAccess('premium_feature')) {
    return <div>You have access!</div>
  }

  return <div>Please upgrade</div>
}
```

## Key Features

* **Type-safe Hooks**: Access billing data with full TypeScript support
* **React Context**: Global state management for billing information
* **Feature Access Control**: Check user access to features
* **Checkout Sessions**: Create and manage payment flows
* **Subscription Management**: Handle subscription lifecycles

## API Reference

### FlowgladProvider

The main provider component that wraps your application. See [FlowgladProvider.tsx](https://github.com/flowglad/flowglad/blob/main/packages/react/src/FlowgladProvider.tsx) for more details

#### Props

```typescript theme={null}
interface FlowgladProviderProps {
  requestConfig?: {
    headers?: Record<string, string>
  }
  baseURL?: string
  betterAuthBasePath?: string
  children: React.ReactNode
}
```

#### Example

```tsx theme={null}
<FlowgladProvider
  requestConfig={{
    headers: {
      'X-Custom-Header': 'value',
    },
  }}
>
  {children}
</FlowgladProvider>
```

### useBilling Hook

Access billing data and functions throughout your application.

#### Load State Helpers

The hook surfaces billing lifecycle metadata you can use to gate UI:

* `loaded: boolean` – `true` once billing data has settled (success or error).
* `errors: Error[] | null` – Populated when the last fetch failed.
* `reload: () => Promise<void>` – Invalidates and refetches billing data.

```tsx theme={null}
const { loaded, errors, reload } = useBilling()

if (!loaded) return <p>Loading billing…</p>
if (errors?.length) {
  return (
    <div>
      <p>Unable to load billing information.</p>
      <button onClick={reload}>Try again</button>
    </div>
  )
}
```

#### Return Value

```typescript theme={null}
interface BillingContext {
  // Customer data
  customer: Customer | null
  
  // Payment methods
  paymentMethods: PaymentMethod[]
  
  // Subscriptions
  currentSubscription: Subscription // The most recently created current subscription for the customer
  currentSubscriptions: Subscription[] // Subscriptions currently active or pending. Length will always be 1 unless your organization settings allow for multiple subscriptions per customer.
  subscriptions: Subscription[] // All subscriptions for the customer
  
  // Purchases
  purchases: Purchase[]
  
  // Invoices
  invoices: Invoice[]
  
  // Catalog and pricing
  pricingModel: PricingModel // The customer's pricing model
  billingPortalUrl: string // URL to the customer's billing portal
  
  // Feature access
  checkFeatureAccess: (featureSlug: string) => boolean
  checkUsageBalance: (
    usageMeterSlug: string,
    options?: { subscriptionId?: string }
  ) => { availableBalance: number } | null
  getProduct: (productSlug: string) => CatalogProduct | null
  getPrice: (priceSlug: string) => Price | null
  
  // Checkout
  createCheckoutSession: (params: CheckoutSessionParams) => Promise<void>
  createAddPaymentMethodCheckoutSession: (
    params: AddPaymentMethodCheckoutParams
  ) => Promise<void>
  createActivateSubscriptionCheckoutSession: (
    params: ActivateSubscriptionCheckoutParams
  ) => Promise<void>
  
  // Subscription management
  cancelSubscription: (params: CancelSubscriptionParams) => Promise<void>
  uncancelSubscription: (params: UncancelSubscriptionParams) => Promise<void>

  // Usage tracking
  createUsageEvent: (params: CreateUsageEventParams) => Promise<UsageEventResult>
  
  // Refresh data
  reload: () => Promise<void>
  loaded: boolean
  errors: Error[] | null
}
```

#### Example Usage

```tsx theme={null}
import { useBilling } from '@flowglad/react'

function FeatureGate({ featureSlug, children }) {
  const { checkFeatureAccess, createCheckoutSession } = useBilling()

  if (!checkFeatureAccess) {
    return <div>Loading...</div>
  }

  if (!checkFeatureAccess(featureSlug)) {
    return (
      <div>
        <p>Upgrade to access this feature</p>
        <button
          onClick={() =>
            createCheckoutSession({
              priceSlug: 'pro_plan',
              successUrl: window.location.href,
              cancelUrl: window.location.href,
              autoRedirect: true,
            })
          }
        >
          Upgrade Now
        </button>
      </div>
    )
  }

  return <>{children}</>
}
```

### usePricing Hook

Fetch public pricing data without requiring authentication. Use this for pricing pages and plan cards where you only need the pricing model.

```tsx theme={null}
import { useBilling, usePricing } from '@flowglad/react'

function PricingCards() {
  const pricingModel = usePricing()
  const { createCheckoutSession } = useBilling()

  if (!pricingModel) {
    return <div>Loading pricing...</div>
  }

  return (
    <div>
      {pricingModel.products.map((product) => {
        const defaultPrice = product.defaultPrice ?? product.prices?.[0]
        if (!defaultPrice) return null

        return (
          <article key={product.id}>
            <h3>{product.name}</h3>
            <p>{product.description}</p>
            <button
              onClick={() =>
                createCheckoutSession({
                  priceSlug: defaultPrice.slug,
                  successUrl: window.location.href,
                  cancelUrl: window.location.href,
                  autoRedirect: true,
                })
              }
            >
              Choose {defaultPrice.name ?? product.name}
            </button>
          </article>
        )
      })}
    </div>
  )
}
```

### createCheckoutSession

Create a new checkout session for subscriptions or one-time payments.

Checkout sessions for specific prices can be made using either `priceSlug` (an identifier that you define), or `priceId` (an identifier Flowglad defines) as a parameter.

`priceSlug` is recommended, so that you don't need to store any references to Flowglad ids in your application database or environment variables.

#### Parameters

```typescript theme={null}
type FrontendProductCreateCheckoutSessionParams = 
  {
    /**
     * Must specify either priceSlug or priceId
     * Slug is encouraged over Id
     */
    priceSlug?: string
    priceId?: string
    successUrl: string
    cancelUrl: string
    /**
     * Whether to automatically redirect to the hosted checkout page after the session is created
     */
    autoRedirect?: boolean
    quantity?: number
    /**
     * the metadata values to set on the object created by this checkout session
     * - subscription.metadata for subscription prices
     * - purchase.metadata for single payment prices
     */
    outputMetadata?: Record<string, any>
    /**
     * the name value to set on the object created by this checkout session
     * - subscription.name for subscription prices
     * - purchase.name for single payment prices
     */
    outputName?: string
  }
```

#### Example

```tsx theme={null}
const handleUpgrade = async () => {
  await createCheckoutSession({
    priceSlug: 'price_premium_monthly',
    successUrl: `${window.location.origin}/billing/success`,
    cancelUrl: `${window.location.origin}/billing`,
    autoRedirect: true,
  })
}
```

### createAddPaymentMethodCheckoutSession

Open a Flowglad-hosted flow that collects and stores a new payment method for the signed-in customer.

#### Parameters

```typescript theme={null}
type FrontendCreateAddPaymentMethodCheckoutSessionParams = {
  successUrl: string
  cancelUrl: string
  autoRedirect?: boolean
  outputMetadata?: Record<string, any>
  outputName?: string
  /**
   * When provided, Flowglad sets the newly created payment method
   * as the default for the given subscription.
   */
  targetSubscriptionId?: string
}
```

#### Example

```tsx theme={null}
await createAddPaymentMethodCheckoutSession({
  successUrl: `${window.location.origin}/billing/payment-methods`,
  cancelUrl: window.location.href,
  autoRedirect: true,
})
```

### createActivateSubscriptionCheckoutSession

Trigger the activation flow for an existing subscription that needs a payment method before billing can start. Activations can happen for subscriptions on trial, or subscriptions where payment is due but has not yet been completed.

#### Parameters

```typescript theme={null}
type FrontendCreateActivateSubscriptionCheckoutSessionParams = {
  /**
   * The subscription to activate.
   */
  targetSubscriptionId: string
  successUrl: string
  cancelUrl: string
  autoRedirect?: boolean
  outputMetadata?: Record<string, any>
  outputName?: string
}
```

#### Example

```tsx theme={null}
await createActivateSubscriptionCheckoutSession({
  targetSubscriptionId: 'sub_123',
  successUrl: `${window.location.origin}/billing/success`,
  cancelUrl: window.location.href,
  autoRedirect: true,
})
```

### cancelSubscription

Cancel a subscription owned by the current customer and refresh billing data.

#### Parameters

```typescript theme={null}
interface CancelSubscriptionParams {
  id: string
  cancellation:
    | { timing: 'at_end_of_current_billing_period' }
    | { timing: 'immediately' }
}
```

#### Example

```tsx theme={null}
await cancelSubscription({
  id: 'sub_123',
  cancellation: {
    timing: 'at_end_of_current_billing_period',
  },
})
```

### uncancelSubscription

Reverse a scheduled subscription cancellation. The subscription must be in `cancellation_scheduled` status. Automatically refreshes billing data on success.

#### Parameters

```typescript theme={null}
interface UncancelSubscriptionParams {
  id: string
}
```

#### Example

```tsx theme={null}
import { useBilling } from '@flowglad/react'
import { useState } from 'react'

function SubscriptionManager() {
  const { uncancelSubscription, currentSubscription } = useBilling()
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  
  const subscription = currentSubscription
  const isScheduledForCancellation = subscription?.status === 'cancellation_scheduled'
  
  const handleUncancel = async () => {
    if (!subscription?.id) return
    
    setIsLoading(true)
    setError(null)
    
    try {
      await uncancelSubscription({ id: subscription.id })
      // Billing data is automatically refreshed
    } catch (err) {
      setError(
        err instanceof Error
          ? err.message
          : 'Failed to uncancel subscription'
      )
    } finally {
      setIsLoading(false)
    }
  }
  
  if (!isScheduledForCancellation) {
    return null
  }
  
  return (
    <div>
      <p>Your subscription is scheduled to cancel on {new Date(subscription.cancelScheduledAt).toLocaleDateString()}</p>
      <button onClick={handleUncancel} disabled={isLoading}>
        {isLoading ? 'Restoring...' : 'Keep My Subscription'}
      </button>
      {error && <p className="error">{error}</p>}
    </div>
  )
}
```

**Behavior:**

* Operation is idempotent. If the subscription is not scheduled to cancel, the method will silently succeed without uncanceling
* For paid subscriptions: requires an active payment method for the subscription
* Automatically refreshes billing data after success

### createUsageEvent

Record a usage event for usage-based billing directly from the client. This is useful for tracking consumption in real-time as users interact with your application.

#### Parameters

```typescript theme={null}
type CreateUsageEventParams = {
  /**
   * Identifier for the usage price or meter (exactly one required)
   */
  priceSlug?: string
  priceId?: string
  usageMeterSlug?: string
  usageMeterId?: string

  /**
   * The quantity of usage to record.
   * Defaults to 1 if not provided.
   */
  amount?: number

  /**
   * The subscription to record usage against.
   * If not provided, uses the customer's current subscription.
   */
  subscriptionId?: string

  /**
   * A unique identifier for this usage event.
   * Auto-generated if not provided. Use for idempotency.
   */
  transactionId?: string

  /**
   * Timestamp (in milliseconds) of when the usage occurred.
   * Defaults to now if not provided.
   */
  usageDate?: number

  /**
   * Additional properties for the usage event.
   * Required for 'count_distinct_properties' aggregation type.
   */
  properties?: Record<string, unknown>
}
```

#### Returns

```typescript theme={null}
// On success
{ usageEvent: { id: string } }

// On error
{ error: { code: string; json: Record<string, unknown> } }
```

#### Example

```tsx theme={null}
import { useBilling } from '@flowglad/react'

function AIChat() {
  const { createUsageEvent } = useBilling()

  const handleSendMessage = async (message: string) => {
    // Process the message and get token count
    const response = await sendToAI(message)
    const tokensUsed = response.tokensConsumed

    // Record the usage
    const result = await createUsageEvent({
      usageMeterSlug: 'ai_tokens',
      amount: tokensUsed,
    })

    if ('error' in result) {
      console.error('Failed to record usage:', result.error)
    }
  }

  return (
    // Chat UI...
  )
}
```

#### Smart Defaults

The client-side `createUsageEvent` provides convenient defaults:

* **`amount`**: Defaults to `1` if not provided
* **`subscriptionId`**: Auto-inferred from the customer's current subscription
* **`transactionId`**: Auto-generated for idempotency if not provided

This means for simple use cases, you only need to provide the price or usage meter identifier:

```tsx theme={null}
// Minimal usage - just specify what you're tracking
await createUsageEvent({ priceSlug: 'api_calls' })

// With custom amount
await createUsageEvent({ usageMeterSlug: 'tokens', amount: 150 })
```

<Note>
  Usage events do not automatically refresh billing data to avoid performance issues with high-frequency events. Call `reload()` manually if you need to update the displayed usage balance.
</Note>

### checkFeatureAccess

Check if the current customer has access to a specific feature.

#### Parameters

* `featureSlug: string` - The slug of the feature to check

#### Returns

* `boolean` - `true` if the customer has access, `false` otherwise

#### Example

```tsx theme={null}
const hasAdvancedAnalytics = checkFeatureAccess('advanced_analytics')

if (hasAdvancedAnalytics) {
  return <AdvancedAnalytics />
}

return <BasicAnalytics />
```

### checkUsageBalance

Get the remaining balance for a usage meter tied to the customer’s subscriptions.

#### Parameters

* `usageMeterSlug: string` – Slug of the usage meter to inspect.
* `options.subscriptionId?: string` – Limit the check to a specific subscription (optional).

#### Returns

* `{ availableBalance: number } | null`

#### Example

```tsx theme={null}
const usage = checkUsageBalance('api_calls')
return usage ? (
  <p>{usage.availableBalance} calls remaining</p>
) : (
  <p>No usage meter found.</p>
)
```

### getProduct

Look up a specific product from the catalog that ships with billing data.

#### Parameters

* `productSlug: string`

#### Returns

* `Product | null`

#### Example

```tsx theme={null}
const proPlan = getProduct('pro')
return proPlan ? <h3>{proPlan.name}</h3> : <p>Product unavailable.</p>
```

### getPrice

Look up a price by slug from the live catalog.

#### Parameters

* `priceSlug: string`

#### Returns

* `Price | null`

#### Example

```tsx theme={null}
const monthly = getPrice('pro-monthly')
return monthly ? (
  <span>
    ${(monthly.unitPrice / 100).toFixed(2)}
    {monthly.intervalUnit ? ` / ${monthly.intervalUnit}` : ''}
  </span>
) : (
  <span>Price not available</span>
)
```

### Other billing records

The billing payload also exposes higher-level billing records you can use to build richer UI:

* `customer` – The Flowglad customer record (including email, name, IDs).
* `subscriptions` – All subscriptions (active and past) returned for the customer.
* `purchases` – Historical purchases for the customer.
* `invoices` – Invoice records (including status and totals).
* `currentSubscription` - The most recently created current subscription for the customer.
* `currentSubscriptions` – Subscriptions currently active or pending.
* `paymentMethods` – Saved payment methods for the customer.
* `billingPortalUrl` – Deep link to Flowglad’s hosted billing portal (if enabled).
* `pricingModel` – Resolved pricing model associated with the customer’s plan.

```tsx theme={null}
const { customer, subscriptions, paymentMethods, billingPortalUrl } = useBilling()

const activeSub = subscriptions?.find((sub) => sub.status === 'active')
const defaultMethod = paymentMethods?.[0]
```

## Common Patterns

### Feature Gating Component

```tsx theme={null}
import { useBilling } from '@flowglad/react'

export function FeatureGate({ 
  featureSlug, 
  fallback, 
  children 
}: {
  featureSlug: string
  fallback?: React.ReactNode
  children: React.ReactNode
}) {
  const { checkFeatureAccess } = useBilling()

  if (!checkFeatureAccess) {
    return null
  }

  if (!checkFeatureAccess(featureSlug)) {
    return fallback || <div>Access denied</div>
  }

  return <>{children}</>
}

// Usage
<FeatureGate 
  featureSlug="premium_feature"
  fallback={<UpgradePrompt />}
>
  <PremiumFeature />
</FeatureGate>
```

### Subscription Status Display

```tsx theme={null}
import { useBilling } from '@flowglad/react'

export function SubscriptionStatus() {
  const { subscriptions, customer } = useBilling()

  if (!subscriptions || subscriptions.length === 0) {
    return <div>No active subscriptions</div>
  }

  const activeSubscription = subscriptions.find(s => s.status === 'active')

  if (!activeSubscription) {
    return <div>No active subscriptions</div>
  }

  return (
    <div>
      <h3>Current Plan</h3>
      <p>Status: {activeSubscription.status}</p>
      <p>Renews: {new Date(activeSubscription.currentPeriodEnd).toLocaleDateString()}</p>
    </div>
  )
}
```

### Custom Upgrade Button

```tsx theme={null}
import { useBilling } from '@flowglad/react'

export function UpgradeButton({ priceSlug }: { priceSlug: string }) {
  const { createCheckoutSession } = useBilling()
  const [isLoading, setIsLoading] = useState(false)

  const handleUpgrade = async () => {
    setIsLoading(true)
    try {
      await createCheckoutSession({
        priceSlug,
        successUrl: `${window.location.origin}/success`,
        cancelUrl: window.location.href,
        autoRedirect: true,
      })
    } catch (error) {
      console.error('Checkout failed:', error)
      setIsLoading(false)
    }
  }

  return (
    <button onClick={handleUpgrade} disabled={isLoading}>
      {isLoading ? 'Loading...' : 'Upgrade'}
    </button>
  )
}
```

## Server Integration

This package requires a backend server running the Flowglad Server SDK. The provider automatically makes requests to `/api/flowglad` by default.

Make sure you have set up the server routes:

* See [Next.js SDK](/sdks/nextjs) for Next.js setup
* See [Server SDK](/sdks/server) for custom integrations

## Next Steps

<CardGroup cols={2}>
  <Card title="Server SDK" icon="server" href="/sdks/server">
    Set up your backend server
  </Card>

  <Card title="Examples" icon="code" href="/sdks/examples">
    See complete code examples
  </Card>
</CardGroup>
