The simplest way to integrate Creem payments into your Next.js application.
Build beautiful checkout experiences with React components, handle webhooks with ease, and manage subscriptions without the headache.
@creem_io/nextjs is the official Next.js integration for Creem - a modern payment platform. This library provides:
- 🎨 React Components - Drop-in components for checkout and customer portal
- 🔐 Type-Safe - Full TypeScript support with comprehensive type definitions
- ⚡ Zero Config - Works out of the box with Next.js App Router
- 🪝 Webhook Management - Simple, type-safe webhook handlers with automatic verification
- 🔄 Subscription Lifecycle - Built-in access management for subscription-based products
Install the package using your preferred package manager:
npm install @creem_io/nextjsyarn add @creem_io/nextjspnpm install @creem_io/nextjs- Next.js 13.0.0 or higher (App Router)
- React 18.0.0 or higher
- A Creem account with API keys
Create a .env.local file in your project root:
CREEM_API_KEY=your_api_key_here
CREEM_WEBHOOK_SECRET=your_webhook_secret_hereCreate app/checkout/route.ts:
import { Checkout } from "@creem_io/nextjs";
export const GET = Checkout({
apiKey: process.env.CREEM_API_KEY!,
testMode: true, // Set to false for production
});In your app/page.tsx or any client component:
"use client";
import { CreemCheckout } from "@creem_io/nextjs";
export default function Page() {
return (
<CreemCheckout productId="prod_abc123" successUrl="/thank-you">
<button className="btn-primary">Subscribe Now</button>
</CreemCheckout>
);
}Create app/api/webhook/creem/route.ts:
import { Webhook } from "@creem_io/nextjs";
export const POST = Webhook({
webhookSecret: process.env.CREEM_WEBHOOK_SECRET!,
onCheckoutCompleted: async ({ customer, product }) => {
console.log(`${customer.email} purchased ${product.name}`);
},
onGrantAccess: async ({ reason, customer, metadata }) => {
const userId = metadata?.referenceId as string;
// Grant user access to your platform
},
onRevokeAccess: async ({ reason, customer, metadata }) => {
const userId = metadata?.referenceId as string;
// Revoke user access from your platform
},
});That's it! You now have a working payment integration in Next.js. 🎉
A React component that creates a checkout link. When clicked, redirects users to your checkout route handler which creates a Creem checkout session.
import { CreemCheckout } from "@creem_io/nextjs";
<CreemCheckout
productId="prod_abc123"
units={2}
discountCode="SUMMER2024"
customer={{
email: "user@example.com",
name: "John Doe",
}}
successUrl="/thank-you"
metadata={{
orderId: "12345",
source: "web",
}}
referenceId="user_123"
>
<button>Buy Now</button>
</CreemCheckout>;Props:
| Prop | Type | Required | Description |
|---|---|---|---|
productId |
string |
✅ | The Creem product ID from your dashboard |
units |
number |
❌ | Number of units to purchase (default: 1) |
discountCode |
string |
❌ | Discount code to apply |
customer |
object |
❌ | Pre-fill customer information (email, name) |
successUrl |
string |
❌ | URL to redirect after successful payment |
metadata |
object |
❌ | Custom metadata to attach to the checkout |
referenceId |
string |
❌ | Your internal user/order ID |
children |
ReactNode |
❌ | Custom button or link content |
A React component that creates a customer portal link for managing subscriptions, payment methods, and billing history.
import { CreemPortal } from "@creem_io/nextjs";
<CreemPortal
customerId="cust_abc123"
returnUrl="/dashboard"
className="btn-secondary"
>
Manage Subscription
</CreemPortal>;Props:
| Prop | Type | Required | Description |
|---|---|---|---|
customerId |
string |
✅ | The Creem customer ID |
returnUrl |
string |
❌ | URL to return to after portal session |
children |
ReactNode |
❌ | Custom button or link content |
| ...linkProps | HTMLAnchorElement |
❌ | Any standard anchor tag props (className, style, etc.) |
Creates a Next.js route handler for checkout sessions.
import { Checkout } from "@creem_io/nextjs";
// app/checkout/route.ts
export const GET = Checkout({
apiKey: process.env.CREEM_API_KEY!,
testMode: true,
defaultSuccessUrl: "/success",
});Options:
| Option | Type | Required | Description |
|---|---|---|---|
apiKey |
string |
✅ | Your Creem API key |
testMode |
boolean |
❌ | Use test environment (default: false) |
defaultSuccessUrl |
string |
❌ | Default success URL if not provided in component |
Creates a Next.js route handler for customer portal sessions.
import { Portal } from "@creem_io/nextjs";
// app/portal/route.ts
export const GET = Portal({
apiKey: process.env.CREEM_API_KEY!,
testMode: true,
});Options:
| Option | Type | Required | Description |
|---|---|---|---|
apiKey |
string |
✅ | Your Creem API key |
testMode |
boolean |
❌ | Use test environment (default: false) |
Creates a Next.js route handler for processing Creem webhooks with automatic signature verification.
import { Webhook } from "@creem_io/nextjs";
// app/api/webhook/creem/route.ts
export const POST = Webhook({
webhookSecret: process.env.CREEM_WEBHOOK_SECRET!,
onCheckoutCompleted: async (data) => {
// Handle one-time purchases
},
onGrantAccess: async (context) => {
// Grant user access (subscription active/trialing/paid)
},
onRevokeAccess: async (context) => {
// Revoke user access (subscription paused/expired)
},
});Options:
| Option | Type | Required | Description |
|---|---|---|---|
webhookSecret |
string |
✅ | Your Creem webhook secret for signature verification |
onCheckoutCompleted |
function |
❌ | Called when checkout is completed |
onRefundCreated |
function |
❌ | Called when refund is created |
onDisputeCreated |
function |
❌ | Called when dispute is created |
onSubscriptionActive |
function |
❌ | Called when subscription becomes active |
onSubscriptionTrialing |
function |
❌ | Called when subscription is trialing |
onSubscriptionPaid |
function |
❌ | Called when subscription payment succeeds |
onSubscriptionExpired |
function |
❌ | Called when subscription expires |
onSubscriptionCanceled |
function |
❌ | Called when subscription is canceled |
onSubscriptionUnpaid |
function |
❌ | Called when subscription payment fails |
onSubscriptionPastDue |
function |
❌ | Called when subscription is past due |
onSubscriptionPaused |
function |
❌ | Called when subscription is paused |
onSubscriptionUpdate |
function |
❌ | Called when subscription is updated |
onGrantAccess |
function |
❌ | Called when user should be granted access |
onRevokeAccess |
function |
❌ | Called when user access should be revoked |
The onGrantAccess and onRevokeAccess callbacks provide a simple way to manage user access for subscription-based products.
Called when a user should be granted access. This happens when:
- Subscription becomes active (after payment)
- Subscription enters trialing period (free trial)
- Subscription payment is paid (renewal)
onGrantAccess: async ({ reason, customer, product, metadata }) => {
const userId = metadata?.referenceId as string;
// Grant access in your database
await db.user.update({
where: { id: userId },
data: { subscriptionActive: true },
});
console.log(`Granted ${reason} to ${customer.email}`);
};Called when a user's access should be revoked. This happens when:
- Subscription is paused (manually by user or admin)
- Subscription is expired (trial ended or canceled subscription period ended)
onRevokeAccess: async ({ reason, customer, product, metadata }) => {
const userId = metadata?.referenceId as string;
// Revoke access in your database
await db.user.update({
where: { id: userId },
data: { subscriptionActive: false },
});
console.log(`Revoked access (${reason}) from ${customer.email}`);
};
⚠️ Important: Both callbacks may be called multiple times for the same user/subscription. Always implement these as idempotent operations (safe to call repeatedly).
Apply discount codes to checkouts:
<CreemCheckout
productId="prod_abc123"
discountCode="LAUNCH50"
successUrl="/thank-you"
>
<button>Subscribe with 50% off</button>
</CreemCheckout>Track your internal IDs and custom data:
<CreemCheckout
productId="prod_abc123"
referenceId={user.id} // Your user ID
metadata={{
orderId: generateOrderId(),
source: "mobile_app",
campaign: "summer_sale",
affiliateId: "partner_123",
}}
successUrl="/thank-you"
>
<button>Subscribe Now</button>
</CreemCheckout>Access in webhooks:
onCheckoutCompleted: async ({ metadata }) => {
const { orderId, source, campaign, affiliateId } = metadata;
// Use your custom data
};Never hardcode API keys or webhook secrets:
// ✅ Good
apiKey: process.env.CREEM_API_KEY!;
// ❌ Bad
apiKey: "sk_live_abc123...";Access callbacks may be called multiple times. Always make them idempotent:
// ✅ Good - idempotent
onGrantAccess: async ({ customer, metadata }) => {
await db.user.upsert({
where: { id: metadata.referenceId },
update: { subscriptionActive: true },
create: { id: metadata.referenceId, subscriptionActive: true },
});
};
// ❌ Bad - not idempotent
onGrantAccess: async ({ customer, metadata }) => {
await db.user.create({
/* will fail on duplicate calls */
});
};Always pass your internal user ID as referenceId:
<CreemCheckout
productId="prod_abc123"
referenceId={session.user.id}
// ...
/>This allows you to easily map Creem customers to your users in webhooks.
Always test your integration in test mode first:
export const GET = Checkout({
apiKey: process.env.CREEM_API_KEY!,
testMode: true, // Test mode
});Webhook handlers should handle errors and return appropriate responses:
onGrantAccess: async (context) => {
try {
await grantUserAccess(context);
} catch (error) {
console.error("Failed to grant access:", error);
// Don't throw - webhook will retry
}
};- Go to your Creem Dashboard
- Navigate to Developers → Webhooks
- Click Add Endpoint
- Enter your webhook URL:
https://yourdomain.com/api/webhook/creem - Select the events you want to receive
- Copy the Webhook Secret to your
.env.localfile
Use a tool like ngrok to expose your local server:
ngrok http 3000Then use the ngrok URL in your Creem webhook settings:
https://abc123.ngrok.io/api/webhook/creem
We welcome contributions! Please reach out on Discord for details.
# Clone the repository
git clone https://github.com/yourusername/creem-nextjs.git
# Install dependencies
npm install
# Build the library
npm run build
# Run example app
cd example
npm install
npm run dev- 📧 Email: support@creem.io
- 💬 Discord: Join our community
- 📚 Documentation: docs.creem.io
Built with ❤️ by the Creem team.