A production-ready chat application template with authentication, conversation persistence, and full ChatBotKit platform integration - built with Next.js, ChatBotKit SDK, next-auth, and shadcn/ui.
Note: This template is deliberately bare-bones. It provides the minimal structure and wiring needed to get a working app, intentionally leaving styling, layout, and architectural choices open so you can build on top without fighting existing opinions.
Building an AI chat product typically means sourcing models, a conversation layer, background processing, storage, a tested abilities catalogue, authentication, security, monitoring, and more from separate systems. The cost adds up fast - not just in money, but in engineering time.
ChatBotKit brings all of this into one platform. This template gets you started with a monetizable chat app where your agents, skills, datasets, third-party integrations, guardrails, and conversation history are all managed through a single API - no need to stitch together disparate services.
- Authentication - Google OAuth via next-auth with JWT sessions
- Protected Routes - Middleware-based route protection for
/chat - Streaming Chat - Real-time AI responses using ChatBotKit streaming with
onStart/onFinishlifecycle hooks - Platform Agents - Select from your ChatBotKit bots via a dropdown (with optional filtering via
CHATBOTKIT_BOT_IDS) - Conversation Persistence - Conversations are saved to the platform and associated with contacts, so users can resume past conversations
- Conversation History - Slide-out sidebar showing previous conversations with auto-generated labels
- Contact Tracking - Authenticated users are automatically mapped to ChatBotKit contacts by email
- Stripe Billing - Subscription checkout, trials, billing portal, and access gating for SaaS-style plans
- Modern UI - Built with shadcn/ui components and Tailwind CSS
- Server Actions - Next.js server actions for secure API communication (API keys never reach the client)
- Next.js 14 - App Router with server actions
- ChatBotKit SDK -
@chatbotkit/reactfor client-side streaming,@chatbotkit/sdkfor server-side API - next-auth - Authentication with Google OAuth provider (extensible to GitHub, email, etc.)
- shadcn/ui - Accessible UI components built on Radix primitives
- Tailwind CSS - Utility-first styling with dark mode support
- Node.js 18+
- A ChatBotKit account with at least one bot configured
- Google OAuth credentials (for authentication)
npx create-cbk-appFollow the prompts and configure environment variables (see below).
# Clone the repository
git clone <repo-url>
cd template-nextjs-chat-auth-stripe-js
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your values (see Environment Variables below)
# Start the development server
npm run devOpen http://localhost:3000 to get started.
| Variable | Required | Description |
|---|---|---|
CHATBOTKIT_API_SECRET |
Yes | ChatBotKit API token from chatbotkit.com/tokens |
CHATBOTKIT_BOT_IDS |
No | Comma-separated bot IDs to show (e.g., bot_abc,bot_def). Omit to show all bots. |
NEXTAUTH_SECRET |
Yes | Random secret for JWT signing - generate with openssl rand -base64 32 |
NEXTAUTH_URL |
Yes | Your app URL (e.g., http://localhost:3000) |
GOOGLE_CLIENT_ID |
Yes | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Yes | Google OAuth client secret |
STRIPE_SECRET_KEY |
Yes* | Stripe secret API key |
STRIPE_PRICE_MONTHLY |
Yes* | Stripe Price ID for your monthly subscription |
STRIPE_PRICE_YEARLY |
No | Stripe Price ID for your yearly subscription |
STRIPE_TRIAL_DAYS |
No | Trial length in days for new subscriptions (default: 14) |
STRIPE_WEBHOOK_SECRET |
No | Stripe webhook signing secret for /api/stripe/webhook |
* Required only when you want to enforce paid access.
Note: The AI model, backstory, skills, datasets, and all other agent configuration is managed per-bot on the ChatBotKit platform. Your app simply references the bot by ID - all capabilities come from the platform.
- Sign up or log in at chatbotkit.com
- Go to chatbotkit.com/tokens and create an API token
- Create at least one bot and configure it with:
- A backstory describing the agent's personality and purpose
- A model (e.g., GPT-5.4 Mini, Claude Sonnet 4.6)
- Optional: a skillset with abilities and datasets for RAG
- Copy the API token to
CHATBOTKIT_API_SECRETin your.envfile - The bot will appear in the agent dropdown. Optionally set
CHATBOTKIT_BOT_IDSto restrict which bots are shown.
- Go to Google Cloud Console
- Create a new OAuth 2.0 Client ID
- Set authorized redirect URI to
http://localhost:3000/api/auth/callback/google - Copy the Client ID and Client Secret to your
.envfile
Browser (React) Server Actions ChatBotKit Platform
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ ConversationManager │──→│ complete() │──→│ streamComplete() │
│ (streaming client) │←──│ onStart() │ │ Bot config + skills│
│ │ │ onFinish() │ │ Datasets + RAG │
│ Bot Selector │──→│ listBots() │──→│ Contacts │
│ Conversation Sidebar│──→│ listConvos() │──→│ Conversations │
└──────────────┘ └──────────────┘ └──────────────────┘
- Authentication - User signs in via Google OAuth. The session is JWT-based (24h expiry).
- Contact Resolution - On first chat load, the user's email is used to ensure a contact exists on the ChatBotKit platform via
cbk.contact.ensure(). - Bot Selection - Available bots are fetched from the platform. The user selects one from the dropdown.
- Streaming - Messages are sent via server actions. The
streamCompletefunction streams the response token-by-token back to the browser. - Persistence -
onStartcreates a conversation on the platform;onFinishsaves all messages and generates a label. The conversation is linked to the contact. - History - The sidebar fetches past conversations for the current contact and allows resuming them.
- Billing Gate - Chat routes and server actions check Stripe subscription/trial status before allowing access.
- Authenticated users who are not on an active/trialing plan are redirected from
/chatto/billing. - The billing page creates Stripe Checkout sessions in subscription mode.
- Returning subscribers can manage plans/cancellations through Stripe Billing Portal.
- Every chat server action re-checks Stripe status, so expired subscriptions cannot continue using chat.
- Optional webhook endpoint (
/api/stripe/webhook) validates Stripe events and revalidates billing/chat routes.
- In Stripe Dashboard, add an endpoint:
https://your-domain.com/api/stripe/webhook - Subscribe to these events:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failedcheckout.session.completed
- Copy the webhook signing secret to
STRIPE_WEBHOOK_SECRET
The chat uses a stateless streaming pattern - the full message history is sent with every request (no server-side session). Persistence is handled via lifecycle callbacks:
onStart- Creates a new conversation on the platform (or reuses an existing one) and links it to the contact and bot.onFinish- Saves all messages to the conversation and generates a human-readable label from the first exchange.
This gives you the simplicity of stateless streaming with the durability of platform-managed conversations.
├── actions/
│ ├── billing.js # Server actions for checkout + portal redirects
│ └── conversation.jsx # Server actions: complete, listBots, listConversations, etc.
├── app/
│ ├── layout.jsx # Root layout with providers
│ ├── page.jsx # Landing page (redirects to /chat or /auth)
│ ├── auth/signin/page.jsx # Custom sign-in page
│ ├── billing/page.jsx # Billing and subscription management page
│ ├── chat/
│ │ ├── page.jsx # Chat page (server component, session gate)
│ │ └── chat-page.jsx # Chat page (client component, all state)
│ └── api/auth/[...nextauth]/
│ └── route.ts # NextAuth API route
│ └── api/stripe/webhook/
│ └── route.js # Stripe webhook validation endpoint
├── components/
│ ├── providers.jsx # Session provider wrapper
│ ├── chat/
│ │ ├── bot-selector.jsx # Agent/bot dropdown selector
│ │ ├── chat-area.jsx # Chat container with ConversationManager
│ │ ├── chat-header.jsx # Header with sidebar toggle and user menu
│ │ ├── chat-input.jsx # Auto-resizing textarea with Enter-to-send
│ │ ├── chat-messages.jsx # Message bubbles with copy-to-clipboard
│ │ └── conversation-sidebar.jsx # Slide-out conversation history panel
│ └── ui/ # shadcn/ui primitives (avatar, button, card, etc.)
├── hooks/
│ └── useAutoRevert.js # Auto-reverting state hook
├── lib/
│ ├── auth-options.js # NextAuth configuration
│ ├── billing.js # Stripe billing status and plan helpers
│ ├── stripe.js # Stripe SDK singleton
│ └── utils.js # cn() utility for Tailwind class merging
└── middleware.ts # JWT-based route protection
The template fetches your bots directly from the ChatBotKit platform. To configure agents:
- Go to chatbotkit.com and create bots
- Configure each bot's backstory, model, skills, datasets, and integrations
- The bots will automatically appear in the agent dropdown
- Optionally set
CHATBOTKIT_BOT_IDSin.envto restrict which bots are shown
When a bot is selected, its full platform configuration is used - including all skills, datasets, connected services, and guardrails. You can update any of these on the platform and the changes take effect immediately, without redeploying your app.
To only show specific bots in the dropdown, set CHATBOTKIT_BOT_IDS:
CHATBOTKIT_BOT_IDS=bot_abc123,bot_def456You can also add client-side functions to the functions array in actions/conversation.jsx. These run alongside the platform-configured skills:
{
name: 'lookupOrder',
description: 'Look up an order by ID',
parameters: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'The order ID' },
},
required: ['orderId'],
},
handler: async ({ orderId }) => {
const order = await fetchOrder(orderId)
return { result: order }
},
},Tip: For most use cases, prefer adding skills on the ChatBotKit platform instead of hardcoding functions. Platform skills can be added, removed, and reconfigured without code changes.
Edit lib/auth-options.js to add GitHub, email, or other providers:
import GitHubProvider from 'next-auth/providers/github'
providers: [
GoogleProvider({ ... }),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],