Skip to main content
NPM Package: @flowglad/server

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

npm install @flowglad/server

Installation

npm install @flowglad/server

Quick Start

1. Set Up Environment Variables

.env
FLOWGLAD_SECRET_KEY="sk_test_..."

2. Create a FlowgladServer Factory Function

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()
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

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 on your frontend. It should handle requests at “/api/flowglad/…“
app/api/flowglad/[...path]/route.ts
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
  },
})

4. Call Server Methods

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

new FlowgladServer(options: FlowgladServerOptions)

Options

See types.ts for more details.
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
  }>
}
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

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.
// 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.
// 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 default pricing model with products, prices, and features.
// 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.
// 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:
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.
// 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:
type CancelSubscriptionParams = {
  id: string
  cancellation:
    | { timing: 'at_end_of_current_billing_period' }
    | { timing: 'immediately' }
}

uncancelSubscription

Reverse a scheduled subscription cancellation.
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
const subscription = await flowglad(userId).uncancelSubscription({
  id: 'sub_123',
})
Parameters:
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.
// 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:
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.
// 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.
// 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.
// Pass YOUR app's user/organization ID, not Flowglad's customer ID
await flowglad(userId).updateCustomer({
  customer: {
    id: 'cust_abc',
    name: 'New Name',
    email: '[email protected]',
  },
})

Feature Access Methods

checkFeatureAccess

Check if the customer has access to a specific feature.
// 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).
// 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:
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

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

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

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

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()
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

Next Steps