Setup
1. Install Packages
Server SDK
npm install @getlumen/server
UI Components
For React apps, you have two options:
Option A: shadcn components (Recommended)
npx shadcn@latest add https://getlumen.dev/pricing-table.json
Option B: Pre-built React package
npm install @getlumen/react
The shadcn components give you full control over styling and can be customized directly or with an LLM of your choice. The React package provides ready-to-use components but it might not suit the styling in your app.
2. Environment Variables
Add these to your environment variables:
# Get these from https://getlumen.dev/developer/apikeys
LUMEN_API_KEY=sk_live_... # Secret key for backend operations
NEXT_PUBLIC_LUMEN_PUBLISHABLE_KEY=pk_live_... # Publishable key for frontend components
If you're not using Next.js, instead of setting NEXT_PUBLIC_LUMEN_PUBLISHABLE_KEY
, you can set the env var as per your framework - for eg, VITE_LUMEN_PUBLISHABLE_KEY
for Vite.
This variable needs to be passed into the UI components later.
3. Backend Proxy Setup
Lumen requires a backend proxy route to securely check entitlements and usage. This prevents direct frontend-to-Lumen communication for sensitive operations.
The flow is: Frontend → Your Backend → Lumen
Create app/api/lumen/[...all]/route.ts
:
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { lumenNextHandler } from "@getlumen/server";
const handler = async (request: NextRequest) => {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json(
await lumenNextHandler({
request,
userId,
})
);
};
export {
handler as GET,
handler as POST,
handler as PUT,
handler as DELETE,
handler as PATCH,
handler as OPTIONS,
handler as HEAD,
};
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";
import { lumenNextHandler } from "@getlumen/server";
const handler = async (request: NextRequest) => {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json(
await lumenNextHandler({
request,
userId: user.id,
})
);
};
export {
handler as GET,
handler as POST,
handler as PUT,
handler as DELETE,
handler as PATCH,
handler as OPTIONS,
handler as HEAD,
};
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { lumenNextHandler } from "@getlumen/server";
const handler = async (request: NextRequest) => {
const session = await auth.api.getSession({ headers: request.headers });
if (!session?.user) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json(
await lumenNextHandler({
request,
userId: session.user.id,
})
);
};
export {
handler as GET,
handler as POST,
handler as PUT,
handler as DELETE,
handler as PATCH,
handler as OPTIONS,
handler as HEAD,
};
For Express, create a catch-all route and use our handler:
import express from 'express';
import { lumenExpressHandler } from '@getlumen/server';
const app = express();
// Your auth middleware (must set req.user or equivalent)
app.use('/api/lumen', requireAuth);
// Ensure JSON parsing for POST/PUT/PATCH bodies
app.use(express.json());
app.all(
'/api/lumen/*',
lumenExpressHandler({
getUserId: (req) => req.user?.id,
mountPath: '/api/lumen',
})
);
For Hono, use the Hono handler with auth middleware:
import { Hono } from 'hono';
import { lumenHonoHandler } from '@getlumen/server';
const app = new Hono();
// Your auth middleware - set userId in context
const requireAuth = async (c, next) => {
// your auth middleware
};
app.all(
'/api/lumen/*',
requireAuth,
lumenHonoHandler({
getUserId: (c) => c.get('userId'),
mountPath: '/api/lumen',
})
);
4. Forward webhooks from your auth provider to Lumen (Optional but Highly Recommended)
Lumen works best if all your customers have a subscription attached, even those on the free plan. This enables entitlements to work (check if a user has a feature or not, grant credits and check usage) and provides a smooth upgrade path to paid plans.
Read the quickstart to see how to create a free plan. You need to set up webhook forwarding from your auth provider to Lumen so that we can automatically create free subscriptions for new users.
Lumen supports webhooks from Clerk, Supabase and Better Auth. If you're using a different auth provider, you'll need to manually callt he Lumen SDK to create a free subscription for new users. See this guide.
-
Navigate to Configure > Webhooks in Clerk dashboard. Create a new webhook subscribed to the event
user.created
. -
Set the webhook URL to:
https://api.getlumen.dev/v1/webhooks/clerk
-
After the webhook is created, click on your new webhook, then on the Advanced tab and add the following header:
- Key:
Authorization
- Value:
Bearer your_lumen_secret_api_key
If you don't have an API Key, you can create one at API Keys.
- Key:
-
Activate webhooks for your project if not already activated
-
Go to Webhooks >
Create a new hook
in your Supabase dashboard triggered on insert intoauth.users
-
Set the webhook URL to:
https://api.getlumen.dev/v1/webhooks/supabase
-
Add this header:
- Header name:
Authorization
- Header value:
Bearer your_secret_lumen_api_key
If you don't have an API Key, you can create one at API Keys.
- Header name:
- Install the Better Auth plugin:
npm install @getlumen/better-auth
- Add the plugin to your Better Auth configuration:
import { betterAuth } from "better-auth";
import { LumenPlugin } from "@getlumen/better-auth";
export const auth = betterAuth({
// ... your other Better Auth configuration
plugins: [
LumenPlugin({
apiKey: process.env.LUMEN_API_KEY!,
}),
],
});
If you don't have an API Key, you can create one at API Keys.
Advanced Usage with Callbacks
import { betterAuth } from "better-auth";
import { LumenPlugin } from "@getlumen/better-auth";
export const auth = betterAuth({
// ... your other Better Auth configuration
plugins: [
LumenPlugin({
apiKey: process.env.LUMEN_API_KEY!,
enabled: true,
prodOnly: process.env.NODE_ENV !== "development",
onSuccess: (user, subscription) => {
console.log(
`Free subscription created for ${user.email}:`,
subscription
);
// Optional: Send notification, update analytics, etc.
},
onError: (user, error) => {
console.error(
`Failed to create free subscription for ${user.email}:`,
error
);
// Optional: Send error notification, log to monitoring service, etc.
},
logger: (message, ...args) => {
console.log(`[FreeSubscription] ${message}`, ...args);
},
}),
],
});
Configuration Options
Option | Type | Default | Description |
---|---|---|---|
apiKey | string | Required | API key for authenticating with the subscription service |
enabled | boolean | true | Whether the plugin is enabled |
prodOnly | boolean | false | Whether to only run in production/staging environments |
onSuccess | function | Optional | Callback function called when subscription is created successfully |
onError | function | Optional | Callback function called when subscription creation fails |
logger | function | console.log | Custom logger function for debugging |
Development vs Production
When prodOnly
is set to true
, the plugin will skip execution in development mode. This is useful for preventing test subscriptions from being created during development.