Skip to main content

Overview

This page provides complete examples and common integration patterns for Flowglad SDKs. For full working applications, check out our example projects.

Example Projects

Generation-based Subscription

A Next.js app with authentication and billing interation, demonstrating a pricing model with subscription, single-payment, and usage. Location: examples/generation-based-subscription Features:
  • Next.js App Router
  • BetterAuth authentication
  • FlowgladProvider setup
  • Pricing table
  • Feature-gated content
  • Usage-based billing for image generation
  • Subscription management
Key Files:
  • pricing.yaml - Pricing model specification
  • src/lib/flowglad.ts - Flowglad server setup
  • src/app/layout.tsx - FlowgladProvider setup
  • src/app/api/flowglad/[...path]/route.ts - Flowglad routeHandler
  • src/app/api/usage-events/route.ts - Route for usage event creation
  • src/components/pricing-cards-grid.tsx - Pricing page with useBilling

Complete Integration Examples

Next.js Setup

Full setup for a Next.js application:
import { FlowgladServer } from '@flowglad/nextjs/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,
      }
    },
  })
}

React SPA with Node.js Backend

Setup for a React application with a separate Node.js backend:
import { FlowgladProvider } from '@flowglad/react'

export default function App() {
  const authToken = getAuthToken()
  return (
    <FlowgladProvider
      loadBilling={!!authToken}
      requestConfig={{
        headers: {
          Authorization: `Bearer ${authToken}`,
        },
      }}
    >
      <Router>
        {/* Your routes */}
      </Router>
    </FlowgladProvider>
  )
}

Common Patterns

Feature Gating

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>Upgrade to access this feature</div>
  }

  return <>{children}</>
}

// Usage
<FeatureGate 
  featureSlug="advanced_analytics"
  fallback={<UpgradePrompt />}
>
  <AdvancedAnalytics />
</FeatureGate>

Usage Metering

import { flowglad } from './flowglad'

export async function trackAPICall(
  endpoint: string,
  customerExternalId: string,
  quantity: number = 1
) {
  // customerExternalId is the ID from YOUR app's database, NOT Flowglad's customer ID
  await flowglad(customerExternalId).createUsageEvent({
    usageMeterId: 'usage_meter_api_calls',
    subscriptionId: 'sub_123',
    priceId: 'price_usage_meter',
    amount: quantity,
    transactionId: `${endpoint}:${Date.now()}`,
    usageDate: Date.now(),
    properties: {
      endpoint,
      requestedAt: new Date().toISOString(),
    },
  })
}

// Usage in API routes
app.get('/api/data', async (req, res) => {
  // Extract customerExternalId from your auth/session
  const userId = await getUserIdFromRequest(req)
  await trackAPICall('/api/data', userId)
  
  const data = await fetchData()
  res.json(data)
})

Subscription Management

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

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

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

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

Pricing Table

import { useBilling } from '@flowglad/react'

interface Plan {
  id: string
  name: string
  priceId: string
  price: number
  features: string[]
}

const plans: Plan[] = [
  {
    id: 'basic',
    name: 'Basic',
    priceId: 'price_basic',
    price: 9.99,
    features: ['Feature 1', 'Feature 2'],
  },
  {
    id: 'premium',
    name: 'Premium',
    priceId: 'price_premium',
    price: 29.99,
    features: ['All Basic features', 'Feature 3', 'Feature 4'],
  },
]

export function PricingTable() {
  const { subscriptions, createCheckoutSession } = useBilling()

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

  const handleSelectPlan = async (priceId: string) => {
    await createCheckoutSession({
      priceId,
      successUrl: `${window.location.origin}/billing/success`,
      cancelUrl: window.location.href,
      autoRedirect: true,
    })
  }

  return (
    <div className="grid grid-cols-2 gap-4">
      {plans.map((plan) => (
        <div key={plan.id} className="border rounded-lg p-6">
          <h3>{plan.name}</h3>
          <p className="text-2xl font-bold">${plan.price}/mo</p>
          
          <ul>
            {plan.features.map((feature) => (
              <li key={feature}>{feature}</li>
            ))}
          </ul>

          <button
            onClick={() => handleSelectPlan(plan.priceId)}
            disabled={currentPlan?.priceId === plan.priceId}
          >
            {currentPlan?.priceId === plan.priceId ? 'Current Plan' : 'Select Plan'}
          </button>
        </div>
      ))}
    </div>
  )
}

Customer Portal

import { useBilling } from '@flowglad/react'

export function CustomerPortal() {
  const { customer, subscriptions, paymentMethods, invoices } = useBilling()

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

  return (
    <div>
      <section>
        <h2>Account Information</h2>
        <p>Name: {customer.name}</p>
        <p>Email: {customer.email}</p>
      </section>

      <section>
        <h2>Subscriptions</h2>
        {subscriptions?.map((sub) => (
          <div key={sub.id}>
            <p>Status: {sub.status}</p>
            <p>Renews: {new Date(sub.currentPeriodEnd).toLocaleDateString()}</p>
          </div>
        ))}
      </section>

      <section>
        <h2>Payment Methods</h2>
        {paymentMethods?.map((pm) => (
          <div key={pm.id}>
            <p>{pm.card?.brand} •••• {pm.card?.last4}</p>
            {pm.isDefault && <span>Default</span>}
          </div>
        ))}
      </section>

      <section>
        <h2>Recent Invoices</h2>
        {invoices?.slice(0, 5).map((invoice) => (
          <div key={invoice.id}>
            <p>{new Date(invoice.createdAt).toLocaleDateString()}</p>
            <p>${(invoice.amount / 100).toFixed(2)}</p>
            <p>{invoice.status}</p>
          </div>
        ))}
      </section>
    </div>
  )
}

Testing Examples

Mock Billing Context

import { FlowgladProvider } from '@flowglad/react'

const mockBillingData = {
  customer: {
    id: 'cus_test',
    externalId: 'user_123',
    name: 'Test User',
    email: 'test@example.com',
  },
  subscriptions: [
    {
      id: 'sub_test',
      status: 'active',
      priceId: 'price_premium',
    },
  ],
}

export function TestWrapper({ children }) {
  return (
    <FlowgladProvider
      loadBilling={false}
      initialData={mockBillingData}
    >
      {children}
    </FlowgladProvider>
  )
}

Next Steps