> ## 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/server

> Core server-side SDK for Flowglad integration in Node.js applications

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

## Overview

The Server SDK is the core package for server-side Flowglad integration. It provides the `FlowgladServer` class that handles all billing operations, customer management, and API communication with Flowglad.

## Installation

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

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

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

## Installation

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

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

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

## Quick Start

### 1. Set Up Environment Variables

```bash .env theme={null}
FLOWGLAD_SECRET_KEY="sk_test_..."
```

### 2. Create a FlowgladServer Factory Function

<CodeGroup>
  ```ts Generic theme={null}
  import { FlowgladServer } from '@flowglad/server'

  export const flowglad = (customerExternalId: string) => {
    // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
    return new FlowgladServer({
      customerExternalId,
      getCustomerDetails: async (customerExternalId) => {
        // Fetch customer details from YOUR database using YOUR app's ID
        const user = await db.users.findOne({ id: customerExternalId })
        if (!user) {
          throw new Error('Customer not found')
        }
        return {
          email: user.email,
          name: user.name,
        }
      },
    })
  }

  // Usage:
  // Pass YOUR app's user/organization ID, not Flowglad's customer ID
  const billing = await flowglad(userId).getBilling()
  ```

  ```ts Example: Better Auth theme={null}
  import { FlowgladServer } from '@flowglad/server'
  import { auth } from '@/utils/auth'

  export const flowglad = (customerExternalId: string) => {
    return new FlowgladServer({
      customerExternalId,
      getCustomerDetails: async (customerExternalId) => {
        const session = await auth.api.getSession()
        if (!session?.user) {
          throw new Error('User not authenticated')
        }
        return {
          email: session.user.email || '',
          name: session.user.name || ''
        }
      },
    })
  }
  ```

  ```ts Example: Supabase Auth theme={null}
  import { FlowgladServer } from '@flowglad/server'
  import { createClient } from '@/utils/supabase/server'

  export const flowglad = (customerExternalId: string) => {
    return new FlowgladServer({
      customerExternalId,
      getCustomerDetails: async (customerExternalId) => {
        const supabase = await createClient()
        const { data: { user } } = await supabase.auth.getUser()
        if (!user) {
          throw new Error('User not authenticated')
        }
        return {
          email: user.email || '',
          name: user.user_metadata.name || user.email || ''
        }
      },
    })
  }
  ```

  ```ts Example: B2B (Organization-based) theme={null}
  import { FlowgladServer } from '@flowglad/server'

  export const flowglad = (organizationId: string) => {
    // For B2B apps: organizationId is ID from YOUR app's database
    return new FlowgladServer({
      customerExternalId: organizationId,
      getCustomerDetails: async (customerExternalId) => {
        // Fetch organization details from YOUR database using YOUR org ID
        const org = await db.organizations.findOne({ id: customerExternalId })
        if (!org) {
          throw new Error('Organization not found')
        }
        return {
          email: org.billingEmail || org.ownerEmail,
          name: org.name,
        }
      },
    })
  }
  ```
</CodeGroup>

<Note>
  **Important:** `customerExternalId` is the ID from **your app's database** (e.g., `user.id` or `organization.id`), **not** Flowglad's customer ID.

  **B2C apps:** Pass `user.id` as `customerExternalId`\
  **B2B apps:** Pass `organization.id` or `team.id` as `customerExternalId`
</Note>

### 3. Mount Flowglad Route Handler

Create a route handler to handle Flowglad API requests from your frontend. The Server SDK provides route handler constructors that enable your client to communicate with your server, allowing you to load billing and feature access data via the [`useBilling` hook](/sdks/react#usebilling-hook) on your frontend. It should handle requests at "/api/flowglad/..."

<Tabs>
  <Tab title="Next.js App Router">
    ```ts title="app/api/flowglad/[...path]/route.ts" theme={null}
    import { nextRouteHandler } from '@flowglad/nextjs/server'
    import { flowglad } from '@/lib/flowglad'
    import { customerIdFromRequest } from '@/lib/auth'

    export const { GET, POST } = nextRouteHandler({
      flowglad,
      getCustomerExternalId: async (req) => {
        const externalId = await customerIdFromRequest(req)
        if (!externalId) {
          throw new Error('Unable to determine customer external ID')
        }
        return externalId
      },
    })
    ```
  </Tab>

  <Tab title="Next.js Pages Router">
    ```ts title="pages/api/flowglad/[...path].ts" theme={null}
    import { pagesRouteHandler } from '@flowglad/nextjs/server'
    import { flowglad } from '@/lib/flowglad'
    import { customerIdFromRequest } from '@/lib/auth'

    export default pagesRouteHandler({
      flowglad,
      getCustomerExternalId: async (req) => {
        const externalId = await customerIdFromRequest(req)
        if (!externalId) {
          throw new Error('Unable to determine customer external ID')
        }
        return externalId
      },
    })
    ```
  </Tab>

  <Tab title="Express">
    ```ts title="server.ts" theme={null}
    import express from 'express'
    import { expressRouter } from '@flowglad/server/express'
    import { flowglad } from './lib/flowglad'
    import { customerIdFromRequest } from './lib/auth'

    const app = express()

    app.use(
      '/api/flowglad',
      expressRouter({
        flowglad,
        getCustomerExternalId: async (req) => {
          const externalId = await customerIdFromRequest(req)
          if (!externalId) {
            throw new Error('Unable to determine customer external ID')
          }
          return externalId
        },
      })
    )
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```ts title="server.ts" theme={null}
    import { requestHandler } from '@flowglad/server'
    import { flowglad } from './lib/flowglad'
    import { customerIdFromRequest } from './lib/auth'
    import type { HTTPMethod } from '@flowglad/shared'

    const flowgladHandler = requestHandler({
      flowglad,
      getCustomerExternalId: async (req) => {
        const externalId = await customerIdFromRequest(req)
        if (!externalId) {
          throw new Error('Unable to determine customer external ID')
        }
        return externalId
      },
    })

    // Example: Hono, Cloudflare Workers, Elysia, etc.
    // Adapt this pattern to your framework
    async function handleFlowgladRequest(request: Request): Promise<Response> {
      const url = new URL(request.url)
      const path = url.pathname
        .replace('/api/flowglad/', '')
        .split('/')
        .filter((segment) => segment !== '')

      const result = await flowgladHandler(
        {
          path,
          method: request.method as HTTPMethod,
          query:
            request.method === 'GET'
              ? Object.fromEntries(url.searchParams)
              : undefined,
          body:
            request.method !== 'GET'
              ? await request.json().catch(() => ({}))
              : undefined,
        },
        request
      )

      return Response.json(
        {
          error: result.error,
          data: result.data,
        },
        {
          status: result.status,
        }
      )
    }
    ```
  </Tab>
</Tabs>

### 4. Call Server Methods

```ts theme={null}
// Fetch billing details (customers, subscriptions, invoices, etc.)
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const billing = await flowglad(userId).getBilling()

// Ensure the Flowglad customer exists
const customer = await flowglad(userId).findOrCreateCustomer()

// Create a hosted checkout session
const checkoutSession = await flowglad(userId).createCheckoutSession({
  priceSlug: 'pro_plan',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
})

// Check feature access for gating premium functionality
const hasPremium = billing.checkFeatureAccess('premium_feature')

// Record metered usage with price (for billing)
await flowglad(userId).createUsageEvent({
  amount: 1,
  priceSlug: 'usage_price_slug',
  subscriptionId: 'subscription_id',
  transactionId: 'idempotency-key',
})

// Record metered usage without price (for tracking only)
await flowglad(userId).createUsageEvent({
  amount: 1,
  usageMeterSlug: 'api_calls',
  subscriptionId: 'subscription_id',
  transactionId: 'idempotency-key',
})
```

## Key Features

* **Customer Management**: Find or create customers automatically
* **Subscriptions**: Create, update, and cancel subscriptions
* **Checkout Sessions**: Generate checkout URLs for payments
* **Feature Access**: Check customer access to features
* **Usage Tracking**: Record usage events and track usage credit grants in real time
* **Invoices**: Retrieve and manage invoices
* **Payment Methods**: Handle customer payment methods
* **Pricing Model**: Access your pricing model with products, prices, and features

## API Reference

### Constructor

```typescript theme={null}
new FlowgladServer(options: FlowgladServerOptions)
```

#### Options

See [types.ts](https://github.com/flowglad/flowglad/blob/main/packages/server/src/types.ts) for more details.

```typescript theme={null}
type FlowgladServerOptions = ScopedFlowgladServerParams

interface ScopedFlowgladServerParams {
  /**
   * The customer ID from YOUR app's database (e.g., user.id or organization.id).
   * This is NOT Flowglad's customer ID—Flowglad uses this external ID to identify
   * and create customers in its system.
   * 
   * For B2C apps: Use your user.id
   * For B2B apps: Use your organization.id or team.id
   */
  customerExternalId: string
  baseURL?: string
  apiKey?: string
  /**
   * Handler to retrieve customer details from your database.
   *
   * This function is called when attempting to create a customer record in Flowglad
   * for a customer that doesn't yet exist. Implement this to fetch the customer's
   * name and email from your database based on their customer ID.
   *
   * @param customerExternalId - The external customer ID from YOUR system (not Flowglad's)
   * @returns Promise resolving to an object containing the customer's name and email
   */
  getCustomerDetails: (customerExternalId: string) => Promise<{
    name: string
    email: string
  }>
}
```

<Note>
  **Important:** `customerExternalId` is the ID from **your app's database** (e.g., `user.id` or `organization.id`), **not** Flowglad's customer ID.

  **B2C apps:** Pass `user.id` as `customerExternalId`\
  **B2B apps:** Pass `organization.id` or `team.id` as `customerExternalId`
</Note>

### Customer Methods

#### `findOrCreateCustomer()`

Find an existing Flowglad customer associated with the current requesting customer (per your app's authentication) or create a new one if none is found.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const customer = await flowglad(userId).findOrCreateCustomer()
```

### Billing Methods

#### `getBilling()`

Get comprehensive billing information for the customer, including helper functions like `checkFeatureAccess`, `checkUsageBalance`, `getProduct`, and `getPrice`.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const billing = await flowglad(userId).getBilling()
const hasPremium = billing.checkFeatureAccess('premium_feature')
const invoiceCount = billing.invoices.length
```

#### `getPricingModel()`

Get the pricing model with products, prices, and features.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const pricingModel = await flowglad(userId).getPricingModel()
// Returns products, prices, and features available for purchase
```

### Subscription Methods

#### `createSubscription`

Create a new subscription for the customer.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const subscription = await flowglad(userId).createSubscription({
  priceSlug: 'pro_plan',
  metadata: {
    source: 'upgrade_flow',
  },
})
```

**Parameters:**

```typescript theme={null}
type CreateSubscriptionParams = {
  customerId: string
  /**
   * Must specify either priceSlug or priceId
   * Slug is encouraged over Id
   */
  priceSlug?: string
  priceId?: string
  /**
   * Quantity of the subscription items.
   * @default 1
   */
  quantity?: number
  startDate?: string
  /**
   * Epoch time in milliseconds of when the trial ends. If not provided, defaults to startDate + the associated price's trialPeriodDays
   */
  trialEnd?: number
  metadata?: Record<string, unknown>
  name?: string
  backupPaymentMethodId?: string
  defaultPaymentMethodId?: string
  interval?: 'day' | 'week' | 'month' | 'year'
  intervalCount?: number
}
```

#### `cancelSubscription`

Cancel an existing subscription.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const canceledSubscription = await flowglad(userId).cancelSubscription({
  id: 'sub_123',
  cancellation: {
    timing: 'at_end_of_current_billing_period',
  },
})
```

**Parameters:**

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

#### `uncancelSubscription`

Reverse a scheduled subscription cancellation.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const subscription = await flowglad(userId).uncancelSubscription({
  id: 'sub_123',
})
```

**Parameters:**

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

**Behavior:**

* If the subscription is not scheduled to cancel, the method will silently succeed without uncanceling (idempotent)
* If the subscription is in a terminal state (e.g., `canceled`), the method will silently succeed without uncanceling
* Only subscriptions in `cancellation_scheduled` status will have their cancellation reversed
* For paid subscriptions: throws error if no payment method exists

**What happens:**

* Status reverts to `active` (or `trialing` if still in trial period)
* The `cancelScheduledAt` field is cleared
* Billing periods marked as `scheduled_to_cancel` are restored
* Any aborted billing runs are rescheduled

### Checkout Methods

#### `createCheckoutSession`

Create a checkout session for payments.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const checkoutSession = await flowglad(userId).createCheckoutSession({
  priceSlug: 'pro_plan',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  outputMetadata: {
    campaign: 'summer_sale',
  },
})

// Redirect user to checkoutSession.url
```

**Parameters:**

```typescript theme={null}
type CreateProductCheckoutSessionParams = {
  /*
   * Must specify either priceSlug or priceId
   * Slug is encouraged over Id
   */
  priceSlug?: string
  priceId?: string
  successUrl: string
  cancelUrl: string
  quantity?: number
  outputMetadata?: Record<string, string>
  outputName?: string
}
```

#### `createAddPaymentMethodCheckoutSession`

Create a checkout session that collects a payment method for the current customer.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const session = await flowglad(userId).createAddPaymentMethodCheckoutSession({
  successUrl: 'https://example.com/payment-methods/success',
  cancelUrl: 'https://example.com/payment-methods/cancel',
  targetSubscriptionId: 'sub_123', // optional
})
```

#### `createActivateSubscriptionCheckoutSession`

Create a checkout session that activates a provisioning or imported subscription.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const activationSession =
  await flowglad(userId).createActivateSubscriptionCheckoutSession({
    targetSubscriptionId: 'sub_123', // optional, attaches the paymentMethod to the subscription for future payments,
    successUrl: 'https://example.com/subscriptions/success',
    cancelUrl: 'https://example.com/subscriptions/cancel',
  })
```

### Customer Updates

#### `updateCustomer`

Update the stored customer record in Flowglad.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
await flowglad(userId).updateCustomer({
  customer: {
    id: 'cust_abc',
    name: 'New Name',
    email: 'new-email@example.com',
  },
})
```

### Feature Access Methods

#### `checkFeatureAccess`

Check if the customer has access to a specific feature.

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const billing = await flowglad(userId).getBilling()
const hasAccess = billing.checkFeatureAccess('advanced_analytics')

if (hasAccess) {
  // Grant access to feature
}
```

### Usage Tracking Methods

#### `createUsageEvent`

Record a usage event for metered billing. You can create usage events with either a price (for tracking usage with billing) or a usage meter (for tracking usage without billing).

```typescript theme={null}
// Pass YOUR app's user/organization ID, not Flowglad's customer ID

// Example: With price (for tracking usage with billing)
const usageEvent = await flowglad(userId).createUsageEvent({
  amount: 125,
  priceSlug: 'price_usage_meter',
  subscriptionId: 'sub_123',
  transactionId: 'txn_unique',
  usageDate: Date.now(),
  properties: {
    endpoint: '/api/data',
  },
})

// Example: With usage meter (for tracking usage without billing)
const usageEvent = await flowglad(userId).createUsageEvent({
  amount: 125,
  usageMeterSlug: 'api_calls',
  subscriptionId: 'sub_123',
  transactionId: 'txn_unique',
  usageDate: Date.now(),
  properties: {
    endpoint: '/api/data',
  },
})
```

**Parameters:**

```typescript theme={null}
type CreateUsageEventParams = 
  {
    amount: number
    /**
     * Must specify exactly one of:
     * - priceSlug or priceId (for billing)
     * - usageMeterSlug or usageMeterId (for tracking usage without billing)
     * Slug is encouraged over Id
     */
    priceSlug?: string
    priceId?: string
    usageMeterSlug?: string
    usageMeterId?: string
    subscriptionId: string
    transactionId: string
    usageDate?: number
    properties?: Record<string, unknown>
  }
```

## Common Patterns

### Feature Gating Middleware

```typescript theme={null}
import { FlowgladServer } from '@flowglad/server'
import { db } from './db'

// Factory function to create scoped instance
function getFlowgladServer(customerExternalId: string) {
  return new FlowgladServer({
    customerExternalId,
    getCustomerDetails: async (id) => {
      const user = await db.users.findOne({ id })
      return { name: user.name, email: user.email }
    },
  })
}

export function requireFeature(featureSlug: string) {
  return async (req, res, next) => {
    try {
      const userId = req.user?.id
      if (!userId) {
        return res.status(401).json({ error: 'Unauthorized' })
      }

      const billing = await flowglad(userId).getBilling()
      const hasAccess = billing.checkFeatureAccess(featureSlug)

      if (!hasAccess) {
        return res.status(403).json({
          error: 'Feature not available',
          upgradeUrl: '/pricing',
        })
      }

      next()
    } catch (error) {
      next(error)
    }
  }
}

// Usage
app.get('/api/premium-endpoint', requireFeature('premium_feature'), (req, res) => {
  res.json({ data: 'premium data' })
})
```

### Usage Metering

```typescript theme={null}
// Example: Track usage with price
export async function trackAPIUsage(endpoint: string, customerExternalId: string) {
  // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
  await flowglad(customerExternalId).createUsageEvent({
    priceSlug: 'price_usage_api_calls',
    subscriptionId: 'sub_current',
    amount: 1,
    transactionId: `api_call_${Date.now()}_${endpoint}`,
    properties: {
      endpoint,
    },
  })
}

// Example: Track usage without price (for tracking usage without billing)
export async function trackAPIUsageNoBilling(endpoint: string, customerExternalId: string) {
  // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
  await flowglad(customerExternalId).createUsageEvent({
    usageMeterSlug: 'api_calls',
    subscriptionId: 'sub_current',
    amount: 1,
    transactionId: `api_call_${Date.now()}_${endpoint}`,
    properties: {
      endpoint,
    },
  })
}

// Usage
app.get('/api/data', async (req, res) => {
  const userId = await getUserIdFromRequest(req)
  await trackAPIUsage('/api/data', userId)
  res.json({ data: 'response' })
})
```

### Customer Portal Generation

```typescript theme={null}
export async function getCustomerPortalData(customerExternalId: string) {
  // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
  const billing = await flowglad(customerExternalId).getBilling()

  return {
    customer: billing.customer,
    activeSubscriptions: billing.subscriptions.filter(
      (s) => s.status === 'active'
    ),
    paymentMethods: billing.paymentMethods,
    recentInvoices: billing.invoices.slice(0, 10),
    features: billing.features,
  }
}
```

## Integration Examples

### Basic Setup

```typescript theme={null}
import { FlowgladServer } from '@flowglad/server'

export 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) => {
      // Fetch customer details from YOUR database using YOUR app's ID
      const user = await db.users.findOne({ id: externalId })
      if (!user) {
        throw new Error('Customer not found')
      }
      return {
        email: user.email,
        name: user.name,
      }
    },
  })
}

// Usage:
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const billing = await flowglad(userId).getBilling()
```

<Note>
  **Important:** `customerExternalId` is the ID from **your app's database** (e.g., `user.id` or `organization.id`), **not** Flowglad's customer ID.

  **B2C apps:** Pass `user.id` as `customerExternalId`\
  **B2B apps:** Pass `organization.id` or `team.id` as `customerExternalId`
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Next.js SDK" icon="react" href="/sdks/nextjs">
    Use with Next.js applications
  </Card>
</CardGroup>
