| name | flowglad-setup |
| description | Install and configure the Flowglad SDK for Next.js, Express, and React applications. Use this skill when adding billing to an app, setting up Flowglad for the first time, or configuring SDK providers and route handlers. |
| license | MIT |
| metadata | {"author":"flowglad","version":"1.0.0"} |
Flowglad Setup
Abstract
This skill covers installing and configuring the Flowglad SDK for Next.js, Express, and React applications. It includes framework detection, package installation, environment setup, server factory creation, route handler setup, and provider configuration.
Table of Contents
- Framework Detection — CRITICAL
- Next.js Setup — CRITICAL
- Express Setup — HIGH
- React Setup (Other Frameworks) — HIGH
- Customer ID Mapping — CRITICAL
- getCustomerDetails Callback — HIGH
1. Framework Detection
Impact: CRITICAL
Before beginning setup, detect which framework the user is using to ensure correct package installation and configuration.
1.1 Detecting the Framework
Impact: CRITICAL (incorrect detection leads to wrong SDK usage)
Check for framework-specific configuration files to determine the correct setup path.
Detection Rules:
Next.js: next.config.js OR next.config.ts OR next.config.mjs exists
Express: "express" in package.json dependencies
React (CRA): "react-scripts" in package.json dependencies
Vite React: "vite" in package.json devDependencies AND "react" in dependencies
Incorrect: assuming Next.js without checking
import { FlowgladServer } from '@flowglad/nextjs/server'
Correct: check framework before recommending packages
ls next.config.* 2>/dev/null && echo "Next.js detected"
grep -q '"express"' package.json && echo "Express detected"
After detection, proceed to the appropriate setup section.
2. Next.js Setup
Impact: CRITICAL
Next.js is the primary supported framework with the most streamlined integration.
2.1 Package Installation
Impact: CRITICAL (wrong packages = broken integration)
Incorrect: installing individual packages separately
npm install @flowglad/server
npm install @flowglad/react
Correct: install the Next.js package (includes server and react)
bun add @flowglad/nextjs @flowglad/react
The @flowglad/nextjs package re-exports server functionality and is designed for Next.js App Router.
2.2 Environment Variables
Impact: CRITICAL (missing env vars = authentication failures)
Incorrect: hardcoding API key
const flowglad = new FlowgladServer({
apiKey: 'sk_live_abc123...',
})
Correct: use environment variable
FLOWGLAD_SECRET_KEY=sk_live_your_secret_key_here
const flowglad = new FlowgladServer({
customerExternalId,
getCustomerDetails,
})
The SDK automatically reads FLOWGLAD_SECRET_KEY from the environment. You only need to pass apiKey explicitly if using a different environment variable name.
2.3 Server Factory Creation
Impact: CRITICAL (incorrect factory = broken billing operations)
Create a factory function that returns a FlowgladServer instance scoped to a specific customer.
Incorrect: creating a single shared instance
export const flowglad = new FlowgladServer({
})
Correct: factory function that creates scoped instances
import { FlowgladServer } from '@flowglad/nextjs/server'
import { db } from '@/db'
export const flowglad = (customerExternalId: string) => {
return new FlowgladServer({
customerExternalId,
getCustomerDetails: async (externalId: string) => {
const user = await db.users.findUnique({
where: { id: externalId },
})
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name || user.email,
}
},
})
}
The factory pattern ensures each request gets a properly scoped server instance.
2.4 API Route Handler
Impact: CRITICAL (missing route = SDK cannot communicate with Flowglad)
Create a catch-all API route to handle Flowglad SDK requests from the frontend.
Incorrect: manual route implementation
export async function POST(req: Request) {
const body = await req.json()
return Response.json({ error: 'Not implemented' })
}
Correct: use nextRouteHandler with catch-all route
import { nextRouteHandler } from '@flowglad/nextjs/server'
import { auth } from '@/lib/auth'
import { flowglad } from '@/lib/flowglad'
export const { GET, POST } = nextRouteHandler({
flowglad,
getCustomerExternalId: async (req) => {
const session = await auth()
if (!session?.user?.id) {
throw new Error('Unauthorized')
}
return session.user.id
},
})
Important: The route must be a catch-all ([...path]) to handle all Flowglad API subroutes.
2.5 FlowgladProvider Setup
Impact: CRITICAL (missing provider = hooks don't work)
Wrap your application with FlowgladProvider to enable the useBilling hook.
Incorrect: not wrapping the app
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Correct: wrap with FlowgladProvider
import { FlowgladProvider } from '@flowglad/react'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<FlowgladProvider>{children}</FlowgladProvider>
</body>
</html>
)
}
For apps with a custom API base URL:
<FlowgladProvider baseURL="https://api.yourapp.com">
{children}
</FlowgladProvider>
3. Express Setup
Impact: HIGH
For Express applications, use the @flowglad/server package with the Express router helper.
3.1 Package Installation
Impact: HIGH (wrong package = missing Express utilities)
Incorrect: installing the Next.js package for Express
npm install @flowglad/nextjs
Correct: install the server package
bun add @flowglad/server
3.2 Server Factory Creation
Impact: HIGH (same pattern as Next.js)
Incorrect: not providing getCustomerDetails
export const flowglad = (customerExternalId: string) => {
return new FlowgladServer({
customerExternalId,
})
}
Correct: provide complete factory
import { FlowgladServer } from '@flowglad/server'
import { db } from '../db'
export const flowglad = (customerExternalId: string) => {
return new FlowgladServer({
customerExternalId,
getCustomerDetails: async (externalId: string) => {
const user = await db.users.findOne({ id: externalId })
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name,
}
},
})
}
3.3 Express Router Setup
Impact: HIGH (incorrect setup = broken API routes)
Incorrect: manually handling each route
import express from 'express'
const router = express.Router()
router.post('/checkout', async (req, res) => {
})
export { router }
Correct: use expressRouter helper
import { expressRouter } from '@flowglad/server/express'
import type { Request } from 'express'
import { flowglad } from '../utils/flowglad'
export const flowgladRouter = expressRouter({
flowglad,
getCustomerExternalId: async (req: Request) => {
const userId = req.user?.id
if (!userId) {
throw new Error('Unauthorized')
}
return userId
},
})
Mount the router in your Express app:
import express from 'express'
import { flowgladRouter } from './routes/flowglad'
const app = express()
app.use(express.json())
app.use('/api/flowglad', flowgladRouter)
app.listen(3000)
4. React Setup (Other Frameworks)
Impact: HIGH
For React apps not using Next.js (Create React App, Vite, etc.), you need both frontend and backend setup.
4.1 Package Installation
Impact: HIGH (frontend package only)
bun add @flowglad/react
4.2 FlowgladProvider Setup
Impact: HIGH (must point to your backend)
Incorrect: using FlowgladProvider without baseURL in non-Next.js apps
import { FlowgladProvider } from '@flowglad/react'
function App() {
return (
<FlowgladProvider>
<MyApp />
</FlowgladProvider>
)
}
Correct: specify your backend URL
import { FlowgladProvider } from '@flowglad/react'
function App() {
return (
<FlowgladProvider baseURL="https://api.yourapp.com">
<MyApp />
</FlowgladProvider>
)
}
4.3 Backend Requirements
Impact: HIGH (frontend SDK requires backend)
The @flowglad/react package makes API calls to your backend. You must have a backend that:
- Handles Flowglad API routes (use
@flowglad/server with Express, Fastify, etc.)
- Authenticates requests and extracts customer IDs
- Forwards requests to Flowglad's API
Incorrect: trying to use Flowglad client-side only
const billing = await fetch('https://api.flowglad.com/v1/...', {
headers: { Authorization: `Bearer ${FLOWGLAD_SECRET_KEY}` },
})
Correct: frontend calls your backend, backend calls Flowglad
Browser (React) → Your Backend (Express/etc) → Flowglad API
↑ ↑ ↑
@flowglad/react @flowglad/server Flowglad servers
5. Customer ID Mapping
Impact: CRITICAL
Flowglad uses customerExternalId to link billing data to your application's users. This is YOUR app's user ID, not a Flowglad-generated ID.
5.1 Using Your App's User ID
Impact: CRITICAL (wrong ID = billing attached to wrong user)
Incorrect: generating a new ID for Flowglad
const flowgladCustomerId = crypto.randomUUID()
export const flowglad = (userId: string) => {
return new FlowgladServer({
customerExternalId: flowgladCustomerId,
})
}
Correct: use your existing user/organization ID
export const flowglad = (customerExternalId: string) => {
return new FlowgladServer({
customerExternalId,
getCustomerDetails: async (externalId) => {
const user = await db.users.findUnique({
where: { id: externalId },
})
return { email: user.email, name: user.name }
},
})
}
const billing = await flowglad(session.user.id).getBilling()
5.2 Organization vs User Customers
Impact: HIGH (affects multi-tenant billing)
For B2B apps with team/organization billing, use the organization ID as the customer ID.
Incorrect: using user ID for organization billing
const getCustomerExternalId = async (req) => {
const session = await auth()
return session.user.id
}
Correct: use organization ID for team billing
const getCustomerExternalId = async (req) => {
const session = await auth()
return session.user.organizationId
}
Choose your customer ID strategy based on your billing model:
| Billing Model | customerExternalId | Example |
|---|
| Per-user (B2C) | user.id | Consumer SaaS |
| Per-team (B2B) | organization.id | Team collaboration tools |
| Per-workspace | workspace.id | Multi-workspace apps |
6. getCustomerDetails Callback
Impact: HIGH
The getCustomerDetails callback is called when Flowglad needs to create a new customer record. It must return the customer's email and name.
6.1 Required Fields
Impact: HIGH (missing fields = customer creation fails)
Incorrect: returning incomplete data
getCustomerDetails: async (externalId) => {
const user = await db.users.findUnique({ where: { id: externalId } })
return {
email: user.email,
}
}
Correct: return both email and name
getCustomerDetails: async (externalId) => {
const user = await db.users.findUnique({ where: { id: externalId } })
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name || user.email,
}
}
6.2 Database Integration
Impact: HIGH (callback must access your user data)
The callback receives the customerExternalId and should look up the corresponding user in your database.
Incorrect: hardcoding customer details
getCustomerDetails: async (externalId) => {
return {
email: 'test@example.com',
name: 'Test User',
}
}
Correct: query your database
import { db } from '@/db'
import { users } from '@/db/schema'
import { eq } from 'drizzle-orm'
getCustomerDetails: async (externalId) => {
const [user] = await db
.select({ email: users.email, name: users.name })
.from(users)
.where(eq(users.id, externalId))
.limit(1)
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name || 'Unknown',
}
}
import { prisma } from '@/lib/prisma'
getCustomerDetails: async (externalId) => {
const user = await prisma.user.findUnique({
where: { id: externalId },
select: { email: true, name: true },
})
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name || user.email,
}
}
Complete Next.js Setup Example
Here's a complete setup for a Next.js application:
1. Install packages:
bun add @flowglad/nextjs @flowglad/react
2. Set environment variable:
FLOWGLAD_SECRET_KEY=sk_live_your_key_here
3. Create server factory (lib/flowglad.ts):
import { FlowgladServer } from '@flowglad/nextjs/server'
import { db } from '@/db'
import { users } from '@/db/schema'
import { eq } from 'drizzle-orm'
export const flowglad = (customerExternalId: string) => {
return new FlowgladServer({
customerExternalId,
getCustomerDetails: async (externalId) => {
const [user] = await db
.select({ email: users.email, name: users.name })
.from(users)
.where(eq(users.id, externalId))
.limit(1)
if (!user) {
throw new Error(`User not found: ${externalId}`)
}
return {
email: user.email,
name: user.name || user.email,
}
},
})
}
4. Create API route (app/api/flowglad/[...path]/route.ts):
import { nextRouteHandler } from '@flowglad/nextjs/server'
import { auth } from '@/lib/auth'
import { flowglad } from '@/lib/flowglad'
export const { GET, POST } = nextRouteHandler({
flowglad,
getCustomerExternalId: async () => {
const session = await auth()
if (!session?.user?.id) {
throw new Error('Unauthorized')
}
return session.user.id
},
})
5. Add FlowgladProvider (app/layout.tsx):
import { FlowgladProvider } from '@flowglad/react'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<FlowgladProvider>{children}</FlowgladProvider>
</body>
</html>
)
}
6. Use in components:
'use client'
import { useBilling } from '@flowglad/react'
export function BillingStatus() {
const { loaded, customer, currentSubscription } = useBilling()
if (!loaded) return <div>Loading...</div>
if (!customer) return <div>Please log in</div>
return (
<div>
<p>Plan: {currentSubscription?.product?.name || 'Free'}</p>
</div>
)
}
References