# Extras (/extras)
# Extras ๐
Level up your project with these hand-picked tools and resources! From UI components to brand assets, we've got you covered. โจ
## shadcn/ui Component Library ๐งฉ
Indie Kit is built on **shadcn/ui**, which means you have access to an incredible ecosystem of components and resources!
### Awesome shadcn/ui Collection
Explore [awesome-shadcn-ui](https://github.com/birobirobiro/awesome-shadcn-ui) - a treasure trove of community-created resources:
* ๐จ **Beautiful Themes** - Pre-made color schemes and styles
* ๐งฉ **Extra Components** - Beyond the default component set
* ๐ ๏ธ **Dev Tools** - Utilities to speed up development
* ๐ **Tutorials** - Learn best practices and advanced patterns
* ๐ฏ **Real Projects** - See shadcn/ui in production apps
* ๐ **Starter Kits** - Boilerplates for specific use cases
This repository is constantly updated with new gems from the community! Bookmark it and check back regularly for fresh components and tools. ๐
## Brand Assets & Design ๐จ
Get your branding sorted in minutes with these free tools!
### Logo Generator โก
Create professional logos with [logofa.st](https://logofa.st/):
* ๐ฏ **AI-Powered** - Generate unique logos instantly
* ๐จ **Fully Customizable** - Adjust colors, fonts, and layouts
* ๐ฆ **Multiple Formats** - SVG, PNG, and more
* โก **Perfect for MVPs** - Launch fast without hiring a designer
Generate multiple variations and A/B test them with your audience before committing! Your logo is often the first thing customers see.
### Favicon Generator ๐ผ๏ธ
Generate pixel-perfect favicons with [RealFaviconGenerator](https://realfavicongenerator.net/):
* ๐ฑ **All Platforms** - iOS, Android, Windows, and web
* โ **Optimal Sizes** - Automatically creates all required dimensions
* ๐จ **Platform-Specific** - Tailored for each device/browser
* ๐ป **Installation Code** - Copy-paste ready HTML
**What you get:**
* Browser tab icon
* iOS home screen icon
* Android splash screen
* Windows tile
* Safari pinned tab icon
Both tools are **100% free** and perfect for indie hackers! Upload your logo, download the assets, and you're done in under 5 minutes. ๐
## Additional Tools ๐ ๏ธ
### Colors & Themes
* [Coolors](https://coolors.co/) - Generate beautiful color palettes
* [Realtime Colors](https://www.realtimecolors.com/) - Visualize your color scheme on real UI
* [shadcn Themes](https://ui.shadcn.com/themes) - Pre-built theme configurations
### Icons & Graphics
* [Lucide Icons](https://lucide.dev/) - Beautiful, consistent icon set (built-in!)
* [Hero Patterns](https://heropatterns.com/) - SVG background patterns
* [Unsplash](https://unsplash.com/) - Free high-quality stock photos
### Typography
* [Google Fonts](https://fonts.google.com/) - Free web fonts
* [Fontjoy](https://fontjoy.com/) - Font pairing suggestions
* [Type Scale](https://typescale.com/) - Generate harmonious font sizes
## Need More? ๐ฌ
Can't find what you're looking for?
* ๐ฌ Ask in the [Indie Kit Discord](https://indiekit.pro/app/discord)
* ๐ Check the [shadcn/ui docs](https://ui.shadcn.com/)
* ๐ Search [npm](https://www.npmjs.com/) for React components
These resources exist to help you launch quickly! Don't spend weeks on design - use these tools to get 80% there in hours, then iterate based on user feedback. ๐ฏ
# Installation (/getting-started)
YouTube Video: https://www.youtube.com/watch?v=Ho9MygCGGuI
import { Card, Cards } from 'fumadocs-ui/components/card';
Let's get Indie Kit running on your local machine! You'll be up and running in under 5 minutes. โก
Make sure you have **Node.js 18+** and **Git** installed. That's all you need!
## Installation Steps โ๏ธ
### 1. Clone the Repository
```bash
git clone https://github.com/Indie-Kit/indie-kit
cd indie-kit
```
Building a B2B SaaS? Clone the B2B Kit instead:
```bash
git clone https://github.com/Indie-Kit/b2b-boilerplate
```
### 2. Install pnpm (if needed)
We use pnpm because it's faster and more efficient:
```bash
npm install -g pnpm
```
### 3. Install Dependencies
```bash
pnpm install
```
### 4. Start Development Server
```bash
pnpm dev
```
### 5. Open in Browser
Visit `http://localhost:3000` and see your app running! ๐
### 6. Reset Git Origin
Disconnect from Indie Kit repo and connect to yours:
```bash
git remote remove origin
git remote add origin YOUR_REPO_URL
```
Your local environment is set up! Start customizing and building your SaaS. โจ
## Project Structure ๐
Understanding where things are helps you move fast:
```
indie-kit/
โโโ src/
โ โโโ app/ # Next.js app router (pages & layouts)
โ โ โโโ api/ # API routes
โ โโโ components/ # React components
โ โโโ lib/ # Utilities, hooks, helpers
โ โโโ db/ # Database schema & queries
โ โโโ emails/ # Email templates
โโโ public/ # Static assets
โโโ .env.local # Environment variables (create this)
```
**Key folders:**
* ๐จ `/src/app` - Your pages and app logic
* ๐งฉ `/src/components` - Reusable UI components
* ๐ ๏ธ `/src/lib` - Utilities and helpers
* ๐๏ธ `/src/db` - Database schema (Drizzle ORM)
* โ๏ธ `/src/emails` - Email templates (React Email)
## Configuration โ๏ธ
### Config File
Open `src/lib/config.ts` to customize your app:
```ts
export const config = {
appName: "Your App Name",
auth: {
enablePasswordAuth: true,
},
// ... more options
}
```
Everything is configured in one place! Update `config.ts` to enable/disable features like auth methods, payment providers, and more.
### Environment Variables
Create `.env.local` for local development:
```bash
# Database
DATABASE_URL="your-database-url"
# Auth
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"
# Email
RESEND_API_KEY="your-api-key"
# Payments (add when ready)
# STRIPE_SECRET_KEY="your-stripe-key"
```
**Environment file hierarchy:**
* ๐ `.env` - Defaults (committed to repo)
* ๐ `.env.local` - Local development (not committed)
* ๐ `.env.production` - Production settings (in your hosting platform)
Never commit `.env.local` to git! It contains sensitive keys and is already in `.gitignore`.
## Next Steps ๐ฏ
Now that you're set up, here's what to do next:
1. **Configure your database** - [Set up PostgreSQL](/setup/database)
2. **Launch your landing page** - [Go live in 5 minutes](/launch-in-5-minutes)
3. **Set up authentication** - [Configure auth providers](/setup/auth)
4. **Add payments** - [Connect Stripe or others](/setup/payments)
You're all set! Pick your next step above and start building your SaaS. ๐
# Overview (/)
import { Card, Cards } from 'fumadocs-ui/components/card';
# Welcome to Indie Kit! ๐
The fastest way to build and launch your SaaS. Everything you need is built-in and ready to go! โจ
Clone the repo, run `pnpm install`, and you're ready to build. No complex setup, no boilerplate fatigue!
## Quick Start ๐
Get Indie Kit running locally in 5 minutes
Go live with a beautiful landing page
Build hype before launch
## Setup & Configuration โ๏ธ
PostgreSQL with Drizzle ORM
Password, magic links, and social logins
Resend, Mailgun, SES, and more
Stripe, LemonSqueezy, PayPal, DodoPayments, Polar.sh
## Key Features ๐
Organizations, roles, and team management
Usage-based pricing for AI apps
AI-ready docs with search and OG images
Scheduled tasks and email sequences
[Get Repository Access โ](https://indiekit.pro/app/repo-access)
# Launch in 5 Minutes (/launch-in-5-minutes)
YouTube Video: https://www.youtube.com/watch?v=dAKKJT3_DJ4
Get your beautiful landing page live and start collecting customers in under 5 minutes! ๐
## What You'll Build ๐จ
A stunning, conversion-optimized landing page with:
* ๐ฏ Hero section with CTA
* ๐ฐ Pricing table (monthly/annual)
* โจ Social proof & testimonials
* โ FAQ section
* ๐ง Email collection (optional)
No design skills needed! Just mix and match pre-built components to create your perfect landing page.
## Let's Build! ๐ ๏ธ
### Step 1: Get the Code Running
If you haven't already, [follow the setup guide](/getting-started) to clone and run locally.
### Step 2: Customize Your Homepage
Open `src/app/(website-layout)/page.tsx` and compose your landing page:
```tsx
import { WebsiteFAQs } from "@/components/website/faqs";
import { CTA2 } from "@/components/website/cta-2";
import { WithWithout } from "@/components/website/with-without";
import Hero2 from "@/components/sections/hero-2";
import CTA1 from "@/components/website/cta-1";
import MonthlyAnnualPricing from "@/components/website/monthly-annual-pricing";
import TextRevealByWord from "@/components/ui/text-reveal";
export default function WebsiteHomepage() {
return (
<>
>
);
}
```
**What each component does:**
* `Hero2` - Eye-catching hero with headline and CTA button
* `CTA1` - Primary call-to-action section
* `MonthlyAnnualPricing` - Pricing table with toggle
* `TextRevealByWord` - Animated testimonial section
* `WithWithout` - Before/after comparison
* `WebsiteFAQs` - Frequently asked questions
* `CTA2` - Final conversion push
Reorder, remove, or duplicate components to match your needs! Check [all available components](/customisation/installed-components) for more options.
### Step 3: Deploy! ๐
Push to GitHub and deploy to Vercel:
```bash
git add .
git commit -m "Launch landing page"
git push
```
That's it! Your landing page is live! ๐
## Next Steps ๐
Now that your page is live:
1. [Customize components](/customisation/components) with your branding
2. [Set up payments](/payments/create-subscription) to start earning
3. [Launch with waitlist](/launch-with-waitlist) to build hype
4. [Add analytics](/analytics) to track conversions
You're now live and ready to accept customers! Share your link and watch the signups roll in. ๐ฐ
# Launch with Waitlist (/launch-with-waitlist)
YouTube Video: https://www.youtube.com/watch?v=CJ2Tpiazm9U
Want to build anticipation for your product? Let's set up a waitlist to gather early users! Here's how to do it step by step. โจ
## Prerequisites ๐
1. **Set Up Your Database** ๐พ
First, make sure your database is configured properly. Follow our [database setup guide](/setup/database) if you haven't already.
2. **Enable Sign In** ๐
```bash
# Add to your .env file
NEXT_PUBLIC_SIGNIN_ENABLED=true
# Run auth setup command
npx auth secret
```
This command will set up secure authentication with proper encryption keys.
3. **Configure Admin Access** ๐
```bash
# Add to your .env file
SUPER_ADMIN_EMAILS=admin@example.com,admin2@example.com
```
> ๐ **Important**
> Make sure to use the email addresses you'll use to sign in as admin.
## Authentication Setup ๐ก๏ธ
4. **Set Up Authentication**
You have two options:
* [Email Authentication Setup Guide](/setup/auth)
* [Google Login Setup Guide](/setup/auth/google-login)
## Managing Your Waitlist ๐
5. **Access Admin Dashboard**
```bash
http://localhost:3000/super-admin/waitlist
```
Here you can:
* View all waitlist signups
* Export user data
6. **Waitlist Landing Page**
```bash
http://localhost:3000/join-waitlist
```
This is where users can:
* Join your waitlist
* Learn about your product
## Automatic Email Notifications ๐ง
7. **Configure Waitlist Emails**
Customize the waitlist API route to send automatic welcome emails:
```tsx
// src/app/api/waitlist/route.ts
import { sendMail } from '@/lib/email/sendMail'
import { NextResponse } from 'next/server'
import { WaitlistWelcome } from '@/emails/waitlist-welcome'
export async function POST(req: Request) {
try {
const body = await req.json()
const { email, name } = body
// Existing waitlist logic here...
// Send welcome email
await sendMail({
to: email,
subject: "Welcome to the Waitlist! ๐",
html: render(
)
})
return NextResponse.json({ success: true })
} catch (error) {
return NextResponse.json(
{ error: "Failed to join waitlist" },
{ status: 500 }
)
}
}
```
> ๐ก **Pro Tip**
> Check out our [Sending Emails tutorial](/setup/email) for more email customization options!
## Changes to your codebase ๐จ
8. **Update CTA Elements**
Modify these files to customize your waitlist messaging:
```tsx
// src/components/website/hero.tsx
```
```tsx
// src/components/layout/header.tsx
```
> ๐ก **Pro Tip**
> Use compelling copy that creates urgency and excitement!
> Examples:
>
> * "Get Early Access"
> * "Join the Exclusive Beta"
> * "Be the First to Know"
## Launch Time ๐
9. **Deploy Your Waitlist**
Ready to go live? Follow our [deployment guide](/deployment) to launch your waitlist.
## Best Practices ๐ซ
1. **Email Communication** ๐ง
* Send a welcome email immediately (automatically)
* Keep users updated on progress (send emails manually or automatically)
* Provide exclusive previews (send emails manually or automatically)
* Customize email templates for your brand (see [Sending Emails tutorial](/setup/email))
2. **Waitlist Management** ๐
* Regularly check your admin dashboard
* Engage with early signups (automatic and manual)
* Plan your onboarding strategy
3. **Marketing Tips** ๐ฏ
* Share on social media
* Create FOMO with limited spots
* Offer early-bird benefits
* Use email campaigns effectively
Now you're ready to launch your product with a waitlist! Build that anticipation and grow your user base! ๐
# Managing Projects (/managing-projects)
import Image from 'next/image'
# Managing Projects ๐
Stay organized and ship faster with Indie Kit's built-in project tracker! Get a proven roadmap with actionable steps to take your project from idea to launch. โจ
This feature is completely optional but highly recommended for new users! It helps you stay on track and not miss important steps.
## What You Get ๐ฏ
**A complete roadmap with:**
* โ **10 proven phases** - From Mindset to Growth
* ๐ **Progress tracking** - See exactly where you are
* โฑ๏ธ **Time estimates** - Know how long each phase takes
* ๐ก **Pro tips** - Learn from experienced indie hackers
* ๐ **Quick links** - Jump to relevant documentation
* ๐ **45+ actionable steps** - Nothing gets forgotten
**Perfect for:**
* ๐ First-time indie hackers
* ๐ฆ Building multiple projects
* ๐ฏ Staying focused and organized
* ๐ก Following a proven process
## How It Works ๐
### Step 1: Create Your Project
Visit the projects page and add your project details:
๐ [Create Your Project](https://indiekit.pro/app/projects)
**What you'll add:**
* **Project Name** - Your app name
* **Kit Used** - Indie Kit, Indie Kit Pro, or B2B Kit
* **Domain/URL** (Optional) - Your project URL
* **Description** (Optional) - What your project does
Track multiple projects simultaneously! Perfect for indie hackers building several side projects.
### Step 2: Follow Your Roadmap
Once created, you'll get access to a comprehensive roadmap with 10 phases:
**The roadmap includes:**
* ๐ก Mindset & Strategy
* ๐ ๏ธ Project Setup
* ๐ Deployment
* ๐จ UX Design
* ๐๏ธ Database Design
* ๐ป UI Implementation
* ๐ณ Payment Integration
* ๐งช Testing
* ๐ Launch
* ๐ Growth
### Step 3: Track Your Progress
Check off steps as you complete them and watch your progress grow!
**Each phase shows:**
* โฑ๏ธ Time estimates
* ๐ Sub-steps checklist
* ๐ก Pro tips from experienced builders
* ๐ Links to relevant documentation
* โ Completion status
## Why It Helps ๐ช
**Stay on track:**
* Never forget critical setup steps
* Follow a proven process that works
* Get helpful tips at each stage
* Know exactly what to do next
**Save time:**
* No more wondering "what's next?"
* Quick links to relevant documentation
* Learn from others' experience
* Avoid common mistakes
**Build confidence:**
* See your progress visually
* Celebrate completing each phase
* Know you're following best practices
* Feel organized and in control
This roadmap is based on what actually works for indie hackers. Follow it to ship faster and avoid common pitfalls!
## Common Questions โ
**Do I have to follow every step?**
No! The roadmap is a helpful guide. Skip or adapt steps based on your project needs.
**Can I track multiple projects?**
Yes! Create separate projects for each of your ideas. Perfect for indie hackers building multiple side projects.
**What if I get stuck?**
Ask for help in [Indie Kit Discord](https://indiekit.pro)! The community is super helpful and responsive.
**Is this required to use Indie Kit?**
No, it's completely optional! But highly recommended for first-time users to stay organized.
**How long does it typically take?**
Most basic SaaS projects take 2-3 weeks following the roadmap. Complex projects may take longer.
## Get Started ๐
Ready to start tracking your project?
๐ **[Create Your First Project](https://indiekit.pro/app/projects)**
Once you create a project, you'll get instant access to:
* ๐ Complete 10-phase roadmap
* โ Interactive checklist with 45+ steps
* ๐ก Pro tips from experienced builders
* ๐ Quick links to documentation
* ๐ Visual progress tracking
Start tracking your project today and ship faster with a proven roadmap! ๐ช
## Related Documentation ๐
* [Launch in 5 Minutes](/launch-in-5-minutes) - Quick start guide
* [Launch with Waitlist](/launch-with-waitlist) - Pre-launch strategy
* [Getting Started](/getting-started) - Installation guide
* [Deployment Guide](/deployment) - Deploy your app
# AI Features (/ai)
# AI Features ๐ค
Add powerful AI capabilities to your SaaS! Process AI tasks in the background for better performance and user experience. โจ
AI tasks can take seconds or minutes. Always process them in the background using Inngest or n8n!
## What You Can Build ๐ฏ
With Indie Kit's AI support:
* ๐จ **Image Generation** - Create images from text prompts
* ๐ **Text Processing** - Summarization, translation, content generation
* ๐ฅ **Video Processing** - Transcription, editing, generation
* ๐ฃ๏ธ **Voice AI** - Text-to-speech, speech-to-text
* ๐ **Content Moderation** - Auto-moderate user content
* ๐ฌ **Chatbots** - AI-powered customer support
* ๐ **Data Analysis** - Extract insights from data
* ๐ฏ **Recommendations** - Personalized suggestions
## Two Approaches ๐
### 1. Inngest Step Functions โก
Built into Indie Kit! Perfect for simple AI tasks.
**Pros:**
* โ Already configured
* โ Automatic retries
* โ Step-by-step tracking
* โ Easy debugging
* โ Free tier available
**Use for:**
* Single AI API calls
* Sequential processing
* Retry-critical operations
[Learn more โ](/ai/pipeline)
### 2. n8n Workflows ๐
Visual workflow builder with 400+ integrations.
**Pros:**
* โ Visual editor
* โ 400+ integrations
* โ Complex logic support
* โ No-code friendly
* โ Self-hostable
**Use for:**
* Multi-service workflows
* Complex conditional logic
* Data transformations
* Team collaboration
[Learn more โ](/ai/n8n)
Simple AI task? Use Inngest. Complex workflow with multiple services? Use n8n. Both work great!
## Quick Start Examples ๐ก
### Image Generation
```ts
// Trigger AI image generation
await inngest.send({
name: "ai/image.generate",
data: {
prompt: "A sunset over mountains",
userId: user.id,
},
})
```
### Text Summarization
```ts
// Trigger text summarization
await inngest.send({
name: "ai/text.summarize",
data: {
text: longDocument,
userId: user.id,
},
})
```
### Content Moderation
```ts
// Check content with AI
await inngest.send({
name: "ai/moderate.content",
data: {
content: userPost,
contentId: post.id,
},
})
```
## Monetize with Credits ๐ฐ
Charge users based on AI usage:
```ts
// Deduct credits before AI processing
await step.run("deduct-credits", async () => {
await deductCredits(userId, "ai_credits", 1)
})
// Then process
await step.run("generate", async () => {
return await generateWithAI(prompt)
})
```
[Set up usage-based pricing โ](/payments/credits-system)
## Best Practices โ
**1. Always Process in Background**
* Never block API responses waiting for AI
* Show "processing" status immediately
* Poll or webhook for results
**2. Handle Failures Gracefully**
* AI APIs can fail or timeout
* Implement retries (automatic with Inngest)
* Provide fallback responses
**3. Manage Costs**
* Track AI API usage
* Set user quotas
* Monitor spending
* Use credits system
**4. Show Progress**
* Loading states
* Progress indicators
* Status updates
* Real-time notifications
## Popular AI APIs ๐
**Text & Chat:**
* OpenAI GPT-4
* Anthropic Claude
* Google Gemini
* Mistral AI
**Image Generation:**
* OpenAI DALL-E
* Stability AI
* Midjourney API
* Replicate
**Voice:**
* ElevenLabs
* OpenAI Whisper
* Google Text-to-Speech
**Moderation:**
* OpenAI Moderation
* Google SafeSearch
* AWS Rekognition
## Next Steps ๐
1. **Choose your approach** - Inngest or n8n
2. **Set up background jobs** - [Configure Inngest](/setup/background-jobs)
3. **Get AI API keys** - OpenAI, Anthropic, etc.
4. **Build your first feature** - Start with image generation
5. **Add credits** - [Monetize with usage-based pricing](/payments/credits-system)
You have everything you need to build AI-powered features! Start simple and expand from there. ๐คโจ
## Related Documentation ๐
* [AI Pipeline](/ai/pipeline) - Background processing patterns
* [n8n Integration](/ai/n8n) - Complex workflows
* [Background Jobs](/setup/background-jobs) - Inngest setup
* [Credits System](/payments/credits-system) - Usage-based pricing
# n8n Integration (/ai/n8n)
# n8n Integration ๐
Build complex AI workflows with [n8n](https://n8n.io) - a powerful workflow automation platform with 400+ integrations! Perfect for multi-step AI pipelines. โจ
n8n is a visual workflow automation tool. Drag-and-drop to build workflows that connect APIs, AI services, databases, and more!
## Integration Flow ๐
Here's how Indie Kit communicates with n8n:
>IndieKit: Submit AI request
IndieKit->>n8n: POST to webhook (async)
IndieKit-->>User: Return "processing" status
n8n->>AI Service: Process with AI
AI Service-->>n8n: Return result
n8n->>IndieKit: POST to callback URL
IndieKit->>Database: Save result
IndieKit->>User: Notify completion
User->>IndieKit: Fetch result
IndieKit->>Database: Get result
Database-->>IndieKit: Return data
IndieKit-->>User: Show result
`}
/>
**Key points:**
* ๐ **Async processing** - User doesn't wait for AI
* ๐ **Callback pattern** - n8n sends results back
* ๐พ **Database storage** - Results saved for later
* ๐ **User notification** - Alert when done
## Why Use n8n with Indie Kit? ๐ค
**Perfect for:**
* ๐ **Multi-step AI pipelines** - Chain multiple AI services
* ๐ **External integrations** - Connect to 400+ services
* ๐จ **Visual workflow design** - No-code/low-code workflows
* ๐ **Complex logic** - Conditional branching, loops, error handling
* ๐ **Data transformation** - Advanced data manipulation
**Example workflows:**
* User uploads image โ Analyze with AI โ Moderate content โ Save results
* New user signs up โ Enrich data โ Add to CRM โ Send welcome email
* Document uploaded โ Extract text โ Summarize โ Translate โ Email results
## Setup n8n ๐
### Option 1: n8n Cloud (Recommended)
1. Sign up at [n8n.cloud](https://n8n.cloud)
2. Create a new workflow
3. Get your webhook URL
4. Connect to Indie Kit (see below)
### Option 2: Self-Hosted
```bash
# Using Docker
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n
```
Visit `http://localhost:5678` to access n8n editor.
n8n Cloud is easier to start with. Self-hosting gives you full control and is free (open source)!
## Integrate with Indie Kit ๐
### Step 1: Create n8n Workflow
1. Open n8n editor
2. Add **Webhook** node as trigger
3. Set to **POST** method
4. Copy the webhook URL
5. Add your processing nodes (AI, transformations, etc.)
6. Add **Respond to Webhook** node at the end
Use **"Respond to Webhook"** node to send results back to Indie Kit! This enables async communication.
### Step 2: Send Data from Indie Kit
```ts
// src/app/api/app/ai/process/route.ts
import withAuthRequired from '@/lib/auth/withAuthRequired'
import { NextResponse } from 'next/server'
export const POST = withAuthRequired(async (req, context) => {
const { data, action } = await req.json()
const userId = context.session.user.id
// Send to n8n webhook
const response = await fetch(process.env.N8N_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action,
data,
userId,
callbackUrl: `${process.env.NEXT_PUBLIC_APP_URL}/api/n8n/callback`,
}),
})
const result = await response.json()
return NextResponse.json({
status: 'processing',
jobId: result.jobId,
})
})
```
### Step 3: Receive Results (Async)
Create callback endpoint to receive n8n results:
```ts
// src/app/api/n8n/callback/route.ts
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const body = await req.json()
const { jobId, userId, result, status } = body
// Verify request (optional but recommended)
const signature = req.headers.get('x-n8n-signature')
if (signature !== process.env.N8N_WEBHOOK_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Save results to database
await saveAIResult({
userId,
jobId,
result,
status,
})
// Notify user (optional)
await notifyUser(userId, {
message: 'Your AI task is complete!',
result,
})
return NextResponse.json({ success: true })
}
```
Send request โ n8n processes โ n8n calls back โ Show results. User doesn't wait for AI processing!
### Step 4: Add Environment Variables
```bash
# .env.local
N8N_WEBHOOK_URL="https://your-n8n-instance.app.n8n.cloud/webhook/your-webhook-id"
N8N_WEBHOOK_SECRET="your-secret-key"
```
## Example n8n Workflow ๐จ
### AI Image Moderation Pipeline
**n8n workflow nodes:**
1. **Webhook** - Receive image URL from Indie Kit
2. **HTTP Request** - Download image
3. **OpenAI** - Analyze image for inappropriate content
4. **IF** - Check moderation score
5. **Set** - Prepare response data
6. **Respond to Webhook** - Send results back
**Indie Kit integration:**
```ts
// Trigger moderation
const response = await fetch(process.env.N8N_WEBHOOK_URL!, {
method: 'POST',
body: JSON.stringify({
imageUrl: uploadedImage.url,
userId: user.id,
callbackUrl: '/api/n8n/moderation-result',
}),
})
```
## Common Patterns ๐ก
### Pattern 1: Fire and Forget
```ts
// Just trigger, don't wait for response
await fetch(N8N_WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({ data }),
})
return Response.json({ status: 'queued' })
```
### Pattern 2: Async with Callback
```ts
// Send job, n8n calls back when done
await fetch(N8N_WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({
data,
callbackUrl: '/api/n8n/callback',
}),
})
return Response.json({ status: 'processing' })
```
### Pattern 3: Poll for Status
```ts
// Client polls API for job status
const pollStatus = async (jobId: string) => {
const res = await fetch(`/api/ai/status/${jobId}`)
const { status, result } = await res.json()
if (status === 'completed') return result
if (status === 'failed') throw new Error('Job failed')
await new Promise(r => setTimeout(r, 2000))
return pollStatus(jobId) // Retry
}
```
## Best Practices โ
**1. Use Webhooks for Communication**
```ts
// โ Good - Webhook communication
await fetch(N8N_WEBHOOK_URL, { ... })
// โ Bad - Trying to call n8n API directly
await n8nClient.execute() // Complex, not recommended
```
**2. Always Include Callback URL**
```ts
// โ Good - n8n can send results back
{
data: yourData,
callbackUrl: '/api/n8n/callback',
userId: user.id,
}
// โ Bad - No way to get results
{ data: yourData }
```
**3. Secure Your Webhooks**
```ts
// โ Good - Verify webhook signature
const signature = req.headers.get('x-n8n-signature')
if (signature !== process.env.N8N_WEBHOOK_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// โ Bad - No verification
```
**4. Handle Errors**
```ts
// โ Good - Try/catch and fallback
try {
const result = await fetch(N8N_WEBHOOK_URL, { ... })
if (!result.ok) throw new Error('n8n error')
} catch (error) {
// Fallback or notify user
await notifyError(error)
}
```
## Learn More About n8n ๐
n8n has extensive documentation and resources:
* ๐ [n8n Documentation](https://docs.n8n.io) - Complete guide
* ๐ [n8n Courses](https://docs.n8n.io/courses) - Free video courses
* ๐ [Integrations](https://n8n.io/integrations) - Browse 400+ integrations
* ๐ก [Templates](https://n8n.io/workflows) - Pre-built workflows
* ๐ฌ [n8n Forum](https://community.n8n.io) - Community support
n8n + Indie Kit = Build complex automation with AI! Perfect for building AI-powered SaaS products.
## Troubleshooting ๐ง
**n8n webhook not responding?**
* Check n8n workflow is activated
* Verify webhook URL is correct
* Test webhook in n8n editor first
* Check n8n execution logs
**Callback not received?**
* Verify callback URL is publicly accessible
* Check "Respond to Webhook" node is connected
* Look at n8n execution history
* Check for network/firewall issues
**Webhook timing out?**
* n8n workflows have timeout limits
* Break long workflows into steps
* Use async pattern with callbacks
* Consider Inngest for very long tasks
## Next Steps ๐
1. **Set up n8n** - Cloud or self-hosted
2. **Create test workflow** - Simple hello world
3. **Integrate with Indie Kit** - Send webhook from API
4. **Build AI pipeline** - Add AI processing nodes
5. **Go to production** - Monitor and optimize
You can now build complex AI workflows with n8n! Start with a simple workflow and expand from there. ๐
## Related Documentation ๐
* [AI Pipeline](/ai/pipeline) - Overview of AI processing
* [Background Jobs](/setup/background-jobs) - Inngest alternative
* [API Calls](/customisation/api-calls) - Working with webhooks
* [n8n Documentation](https://docs.n8n.io) - Complete n8n guide
# AI Pipeline (/ai/pipeline)
# AI Pipeline ๐ค
Build powerful AI features with background processing! Use Inngest or n8n to handle AI tasks asynchronously for better performance. โจ
AI tasks like image generation, video processing, or LLM calls can take seconds or minutes. Process them in the background for better UX!
## Choose Your Approach ๐ฏ
Two powerful options for AI processing:
### 1. Inngest Step Functions โก
**Best for:**
* ๐จ AI image generation
* ๐ฅ Video processing
* ๐ Text generation
* ๐ Multi-step AI workflows
* ๐ Automatic retries
**Why Inngest:**
* โ Built into Indie Kit
* โ Automatic retries on failures
* โ Step-by-step progress tracking
* โ Easy to debug locally
* โ No external service needed
### 2. n8n Workflows ๐
**Best for:**
* ๐ Complex multi-service workflows
* ๐ Integration with 400+ external services
* ๐ฏ Visual workflow builder
* ๐ Advanced data transformation
* ๐ Conditional branching
**Why n8n:**
* โ Visual workflow editor
* โ Massive integration library
* โ Advanced workflow logic
* โ Great for non-developers
* โ Self-hosted or cloud
Inngest for simple AI tasks, n8n for complex integrations. They work great together!
## Using Inngest for AI ๐
### Example: AI Image Generation
```ts
// src/lib/inngest/functions/generate-image.ts
import { inngest } from "@/lib/inngest/client"
import { openai } from "@/lib/ai/openai"
export const generateImage = inngest.createFunction(
{ id: "generate-ai-image" },
{ event: "ai/image.generate" },
async ({ event, step }) => {
const { prompt, userId } = event.data
// Step 1: Deduct credits
await step.run("deduct-credits", async () => {
await deductCredits(userId, "image_generation", 1)
})
// Step 2: Generate image (retries on failure)
const imageUrl = await step.run("generate-image", async () => {
const response = await openai.images.generate({
prompt,
n: 1,
size: "1024x1024",
})
return response.data[0].url
})
// Step 3: Upload to S3
const finalUrl = await step.run("upload-to-s3", async () => {
const uploaded = await uploadToS3(imageUrl)
return uploaded.url
})
// Step 4: Save to database
await step.run("save-to-db", async () => {
await saveImage({
userId,
prompt,
url: finalUrl,
})
})
return { imageUrl: finalUrl }
}
)
```
**Trigger from your API:**
```ts
// src/app/api/app/generate-image/route.ts
import { inngest } from "@/lib/inngest/client"
import withAuthRequired from "@/lib/auth/withAuthRequired"
export const POST = withAuthRequired(async (req, context) => {
const { prompt } = await req.json()
const userId = context.session.user.id
// Trigger background job
await inngest.send({
name: "ai/image.generate",
data: { prompt, userId },
})
return Response.json({
status: "processing",
message: "Image generation started!"
})
})
```
Each step retries independently! If S3 upload fails, it won't regenerate the image - just retry the upload.
### Example: AI Text Processing
```ts
export const processText = inngest.createFunction(
{ id: "process-text-with-ai" },
{ event: "ai/text.process" },
async ({ event, step }) => {
const { text, userId, action } = event.data
// Deduct credits
await step.run("deduct-credits", async () => {
await deductCredits(userId, "ai_processing", 1)
})
// Process with AI
const result = await step.run("ai-process", async () => {
if (action === "summarize") {
return await openai.chat.completions.create({
model: "gpt-4",
messages: [
{ role: "system", content: "Summarize this text:" },
{ role: "user", content: text }
],
})
}
// Other actions...
})
// Save result
await step.run("save-result", async () => {
await saveAIResult({
userId,
input: text,
output: result.choices[0].message.content,
action,
})
})
return result
}
)
```
## Using n8n for AI ๐
For complex AI workflows with multiple services, [use n8n integration](/ai/n8n)!
**What n8n enables:**
* ๐ Multi-step AI pipelines
* ๐ Integration with 400+ services
* ๐จ Visual workflow builder
* ๐ Advanced data transformation
* ๐ Conditional logic and branching
[Read the n8n integration guide โ](/ai/n8n)
## Best Practices โ
**1. Always Use Background Jobs**
```tsx
// โ Good - Background processing
await inngest.send({ name: "ai/generate" })
return Response.json({ status: "processing" })
// โ Bad - Blocking request
const result = await generateAI() // Takes 30 seconds!
return Response.json(result) // User waits...
```
**2. Deduct Credits First**
```ts
// โ Good - Deduct before processing
await deductCredits(userId, "ai", 1)
await generateImage() // Then process
// โ Bad - Deduct after (might fail)
await generateImage()
await deductCredits(userId, "ai", 1)
```
**3. Handle Errors Gracefully**
```ts
await step.run("ai-call", async () => {
try {
return await openai.complete(prompt)
} catch (error) {
console.error("AI failed:", error)
throw error // Triggers retry
}
})
```
**4. Show Progress to Users**
```tsx
// Poll for status
const checkStatus = async () => {
const res = await fetch('/api/ai/status/job-id')
const { status, result } = await res.json()
if (status === 'completed') {
showResult(result)
} else {
setTimeout(checkStatus, 2000) // Check again
}
}
```
## Common AI Patterns ๐ฏ
### Image Generation Pipeline
1. User submits prompt
2. Deduct credits
3. Generate image with AI
4. Upload to S3
5. Save to database
6. Notify user (email or real-time)
### Content Moderation
1. User posts content
2. Send to moderation AI
3. Check for violations
4. Flag or approve automatically
5. Notify moderators if needed
### Summarization Pipeline
1. User uploads document
2. Extract text from PDF/DOCX
3. Chunk into smaller pieces
4. Summarize each chunk
5. Combine summaries
6. Save and return result
## Next Steps ๐
1. **Choose your tool** - Inngest for simple, n8n for complex
2. **Set up background jobs** - Configure [Inngest](/setup/background-jobs)
3. **Build your first AI feature** - Start with image generation
4. **Add credits system** - [Usage-based pricing](/payments/credits-system)
5. **Monitor & optimize** - Track costs and performance
You can now build powerful AI features with background processing! Start simple and expand from there. ๐ค
## Related Documentation ๐
* [n8n Integration](/ai/n8n) - Complex AI workflows
* [Background Jobs](/setup/background-jobs) - Inngest setup
* [Credits System](/payments/credits-system) - Usage-based pricing
* [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns
# API Calls (/customisation/api-calls)
# Making API Calls ๐
Learn how to build powerful features by making API calls in your Indie Kit application! We'll cover public and protected endpoints with practical examples. โจ
## Understanding API Routes ๐ฏ
Indie Kit uses Next.js App Router API routes. All API endpoints live in the `src/app/api/` directory:
```
src/app/api/
products/route.ts โ Public endpoint
app/user-settings/route.ts โ Protected endpoint (requires auth)
```
* **Public endpoints**: Anyone can access (e.g., blog posts, products)
* **Protected endpoints**: Require authentication (e.g., user settings, admin actions)
## Public API Calls ๐
Public endpoints don't require authentication. Use them for data that anyone can view.
### Step 1: Create the API Endpoint
Create a new file at `src/app/api/products/route.ts`:
```tsx
// src/app/api/products/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
// In a real app, fetch from your database
const products = [
{ id: 1, name: 'Product 1', price: 99 },
{ id: 2, name: 'Product 2', price: 149 },
]
return NextResponse.json(products)
}
```
### Step 2: Fetch Data in Your Component
Use SWR to fetch data with automatic caching and revalidation:
```tsx
// src/app/products/page.tsx
'use client'
import useSWR from 'swr'
export default function ProductsPage() {
const { data, error, isLoading } = useSWR('/api/products')
if (error) return
Failed to load products
if (isLoading) return
Loading...
return (
Products
{data.map((product) => (
{product.name} - ${product.price}
))}
)
}
```
SWR (Stale-While-Revalidate) is a data fetching library that comes pre-configured in Indie Kit. It provides automatic caching, revalidation, and loading states out of the box!
## Protected API Calls ๐
Protected endpoints require authentication. Use them for user-specific data or admin actions.
Always use `withAuthRequired` wrapper for protected endpoints. This ensures only authenticated users can access the data!
### Step 1: Create Protected API Endpoint
Create a protected endpoint at `src/app/api/app/user-settings/route.ts`:
```tsx
// src/app/api/app/user-settings/route.ts
import { withAuthRequired } from '@/lib/auth/withAuthRequired'
import { NextResponse } from 'next/server'
import { db } from '@/db'
import { users } from '@/db/schema'
import { eq } from 'drizzle-orm'
// GET user settings
export const GET = withAuthRequired(async (req, context) => {
const { session } = context
// Fetch settings from database
const userSettings = await db
.select()
.from(users)
.where(eq(users.id, session.user.id))
.limit(1)
return NextResponse.json({
userId: session.user.id,
theme: userSettings[0]?.theme || 'light',
notifications: userSettings[0]?.notifications || true,
})
})
// POST to update settings
export const POST = withAuthRequired(async (req, context) => {
const { session } = context
const body = await req.json()
// Update in database
await db
.update(users)
.set({ theme: body.theme })
.where(eq(users.id, session.user.id))
return NextResponse.json({ success: true })
})
```
### Step 2: Fetch and Update Data
Use SWR for fetching and `mutate` for optimistic updates:
```tsx
// src/app/settings/page.tsx
'use client'
import useSWR, { mutate } from 'swr'
import { toast } from 'sonner'
export default function SettingsPage() {
const { data, error, isLoading } = useSWR('/api/app/user-settings')
const updateSettings = async (newSettings: { theme: string }) => {
try {
// Optimistically update the UI
mutate(
'/api/app/user-settings',
{ ...data, ...newSettings },
false
)
// Make the API call
const response = await fetch('/api/app/user-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSettings),
})
if (!response.ok) throw new Error('Failed to update')
// Revalidate to get fresh data
mutate('/api/app/user-settings')
toast.success('Settings updated!')
} catch (error) {
// Rollback on error
mutate('/api/app/user-settings')
toast.error('Failed to update settings')
}
}
if (error) return
Failed to load settings
if (isLoading) return
Loading...
return (
User Settings
Current theme: {data.theme}
)
}
```
## SWR Features & Patterns ๐ฏ
SWR comes pre-configured in Indie Kit with powerful features:
### Basic Fetching
```tsx
const { data, error, isLoading } = useSWR('/api/endpoint')
```
### Type-Safe Fetching
```tsx
interface Product {
id: number
name: string
price: number
}
const { data, error } = useSWR('/api/products')
```
### Conditional Fetching
Only fetch when certain conditions are met:
```tsx
// Only fetch when user is logged in
const { data } = useSWR(session ? '/api/app/profile' : null)
// Only fetch with valid ID
const { data } = useSWR(
productId ? `/api/products/${productId}` : null
)
```
### Revalidation
```tsx
import { mutate } from 'swr'
// Revalidate specific endpoint
mutate('/api/products')
// Revalidate all endpoints matching pattern
mutate((key) => typeof key === 'string' && key.startsWith('/api/products'))
```
### Optimistic Updates
Update the UI immediately, then sync with server:
```tsx
const addProduct = async (newProduct) => {
// Update UI optimistically
mutate('/api/products', [...data, newProduct], false)
// Make API call
await fetch('/api/products', {
method: 'POST',
body: JSON.stringify(newProduct)
})
// Revalidate to sync with server
mutate('/api/products')
}
```
SWR has many more features! Check out the [official documentation](https://swr.vercel.app/) for pagination, infinite loading, and more.
## Common Patterns ๐
### Loading with Skeleton
```tsx
import { Skeleton } from "@/components/ui/skeleton"
export default function ProductsPage() {
const { data, isLoading } = useSWR('/api/products')
if (isLoading) {
return (
return
}
```
### Mutation with Error Handling
```tsx
const deleteProduct = async (id: number) => {
try {
// Optimistic update
const newData = data.filter(p => p.id !== id)
mutate('/api/products', newData, false)
// Make API call
const response = await fetch(`/api/products/${id}`, {
method: 'DELETE'
})
if (!response.ok) throw new Error('Delete failed')
toast.success('Product deleted!')
} catch (error) {
// Rollback on error
mutate('/api/products')
toast.error('Failed to delete product')
}
}
```
## HTTP Methods ๐
Use the appropriate HTTP method for each action:
### GET - Fetch Data
```tsx
export async function GET() {
const data = await fetchFromDatabase()
return NextResponse.json(data)
}
```
### POST - Create New Resource
```tsx
export async function POST(req: Request) {
const body = await req.json()
const newItem = await createInDatabase(body)
return NextResponse.json(newItem)
}
```
### PUT - Update Entire Resource
```tsx
export async function PUT(req: Request) {
const body = await req.json()
const updated = await updateInDatabase(body)
return NextResponse.json(updated)
}
```
### PATCH - Partial Update
```tsx
export async function PATCH(req: Request) {
const body = await req.json()
const patched = await partialUpdateInDatabase(body)
return NextResponse.json(patched)
}
```
### DELETE - Remove Resource
```tsx
export async function DELETE(req: Request) {
await deleteFromDatabase()
return NextResponse.json({ success: true })
}
```
## Best Practices ๐ก
### 1. Always Handle Loading and Error States
```tsx
const { data, error, isLoading } = useSWR('/api/endpoint')
if (error) return
if (isLoading) return
return
```
### 2. Use TypeScript for Type Safety
```tsx
interface User {
id: string
name: string
email: string
}
const { data } = useSWR('/api/app/profile')
// data is now typed as User
```
### 3. Implement Optimistic Updates
Make your app feel instant:
```tsx
// Update UI first
mutate('/api/todos', [...todos, newTodo], false)
// Then make API call
await createTodo(newTodo)
// Finally revalidate
mutate('/api/todos')
```
### 4. Validate Input Server-Side
```tsx
export const POST = withAuthRequired(async (req, context) => {
const body = await req.json()
// Validate input
if (!body.name || body.name.length < 3) {
return NextResponse.json(
{ error: 'Name must be at least 3 characters' },
{ status: 400 }
)
}
// Process valid data
// ...
})
```
### 5. Use Proper Error Status Codes
```tsx
// 400 - Bad Request (invalid input)
return NextResponse.json({ error: 'Invalid data' }, { status: 400 })
// 401 - Unauthorized (not logged in)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
// 403 - Forbidden (logged in but no permission)
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
// 404 - Not Found
return NextResponse.json({ error: 'Not found' }, { status: 404 })
// 500 - Server Error
return NextResponse.json({ error: 'Server error' }, { status: 500 })
```
## Security Checklist ๐
* โ Use `withAuthRequired` for protected endpoints
* โ Validate all input data server-side
* โ Never trust client-side data
* โ Use proper HTTP methods
* โ Return appropriate error codes
* โ Don't expose sensitive data in error messages
* โ Rate limit API endpoints if needed
* โ Use HTTPS in production
## Troubleshooting ๐ง
### "useSWR is not defined"
Make sure your component has `'use client'` at the top:
```tsx
'use client'
import useSWR from 'swr'
```
### "401 Unauthorized" on Protected Routes
Ensure you're logged in and using `withAuthRequired`:
```tsx
export const GET = withAuthRequired(async (req, context) => {
// Your code here
})
```
### Data Not Updating
Force revalidation after mutations:
```tsx
await updateData()
mutate('/api/endpoint') // Add this
```
### CORS Errors
Next.js API routes don't have CORS issues since they're same-origin. If calling external APIs, handle CORS server-side.
## Next Steps ๐
Now you know how to:
* โ Create public and protected API endpoints
* โ Fetch data with SWR
* โ Handle loading and error states
* โ Implement optimistic updates
* โ Use proper HTTP methods
* โ Secure your endpoints
You're ready to build powerful features with API calls! Start with a simple endpoint and expand from there. ๐
## Related Documentation ๐
* [Protected Pages](/customisation/private-page) - Authentication for pages
* [Pre-installed Components](/customisation/installed-components) - UI components
* [SWR Documentation](https://swr.vercel.app/) - Data fetching
* [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
# Community Components (/customisation/components)
# Access 600+ Components ๐จ
Indie Kit is built on [shadcn/ui](https://ui.shadcn.com/), giving you access to a massive ecosystem of beautiful, customizable components! โจ
## What is shadcn/ui? ๐ค
shadcn/ui is not a traditional component library. Instead of installing packages, you **copy the code directly into your project**. This means:
* โ **Full Control**: Components live in your codebase - modify them however you want
* โ **No Dependencies**: No bloated node\_modules from component libraries
* โ **Easy Customization**: Change styles, behavior, or structure as needed
* โ **Type Safe**: Built with TypeScript for better developer experience
* โ **Accessible**: All components follow accessibility best practices
Indie Kit already includes many components out of the box! Check out the [Pre-installed Components](/customisation/installed-components) to see what's available.
## Community Components ๐
Beyond the official shadcn/ui components, there's a thriving community creating amazing additions!
### 21st.dev - 600+ Premium Components
Visit [21st.dev](https://21st.dev/) to explore:
* ๐จ **UI Elements**: Buttons, cards, modals, dropdowns
* ๐ฑ **Mobile Components**: Touch-optimized mobile interfaces
* ๐ฅ๏ธ **Dashboard Layouts**: Admin panels and analytics views
* ๐ฏ **Marketing Sections**: Hero sections, features, pricing tables
* ๐ **Charts & Graphs**: Data visualization components
* ๐ **Authentication**: Login forms, signup flows, OAuth buttons
* ๐ณ **Payment Forms**: Stripe-ready checkout components
* ๐ **Tables**: Advanced data tables with sorting and filtering
* ๐ญ **Animations**: Beautiful transitions and effects
* ๐ **Forms**: Complex form layouts and validation
## How to Install Components ๐ก
### Step 1: Find a Component
1. Browse [shadcn/ui](https://ui.shadcn.com/) or [21st.dev](https://21st.dev/)
2. Find the component you want to use
3. Click on it to see the installation command
### Step 2: Install with pnpm
Most components will show an `npx` command, but since Indie Kit uses **pnpm**, use this instead:
```bash
pnpm dlx shadcn@latest add [component-name]
```
**Examples:**
```bash
# Add a calendar component
pnpm dlx shadcn@latest add calendar
# Add a data table
pnpm dlx shadcn@latest add table
# Add a chart
pnpm dlx shadcn@latest add chart
```
Indie Kit uses **pnpm** for package management. Always use `pnpm dlx` instead of `npx` when installing shadcn components.
### Step 3: Import and Use
After installation, import the component in your page or component:
```tsx
import { Calendar } from "@/components/ui/calendar";
export default function MyPage() {
return (
);
}
```
### Adding a Data Table
```bash
# Install table component
pnpm dlx shadcn@latest add table
```
```tsx
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function UsersPage() {
return (
NameEmailStatusJohn Doejohn@example.comActive
);
}
```
### Adding Charts
```bash
# Install chart component
pnpm dlx shadcn@latest add chart
```
```tsx
import { Bar, BarChart } from "recharts";
import { ChartContainer } from "@/components/ui/chart";
const data = [
{ month: "Jan", revenue: 4000 },
{ month: "Feb", revenue: 3000 },
{ month: "Mar", revenue: 5000 },
];
export default function AnalyticsPage() {
return (
);
}
```
## Where Components Are Installed ๐
When you run the install command, components are added to:
```
src/
components/
ui/
calendar.tsx โ Your new component
table.tsx
chart.tsx
...
```
All components are added to your project, not as npm packages. This means you can modify them freely to match your exact needs!
## Customizing Components ๐จ
Since the code is in your project, you can customize anything:
### Change Colors
```tsx
// Edit src/components/ui/button.tsx
```
### Modify Behavior
```tsx
// Add custom logic
const handleClick = () => {
// Your custom behavior
console.log("Button clicked!");
};
```
### Add Props
```tsx
// Extend component props
interface CustomButtonProps extends ButtonProps {
icon?: React.ReactNode;
}
```
## Best Practices ๐ก
1. **Start with Pre-installed Components**: Check what Indie Kit already includes before adding new ones
2. **Install Only What You Need**: Don't bloat your codebase with unused components
3. **Customize After Installation**: Modify components to match your brand
4. **Keep Components Organized**: Group related components in logical folders
5. **Document Custom Changes**: Add comments if you heavily modify a component
## Troubleshooting ๐ง
**"Command not found"**
* Make sure you have pnpm installed: `npm install -g pnpm`
* Use `pnpm dlx` instead of `npx`
**"Component not working"**
* Check if all dependencies are installed: `pnpm install`
* Verify imports are correct
* Make sure Tailwind CSS is configured properly
**"Styling looks wrong"**
* Check your `tailwind.config.ts` includes the component paths
* Verify your `globals.css` has the required CSS variables
* Try clearing your build cache: `pnpm run build`
With 600+ community components plus the ability to customize everything, you can build any UI you imagine! ๐
## Related Documentation ๐
* [shadcn/ui Official Docs](https://ui.shadcn.com/) - Browse all official components
* [21st.dev Component Library](https://21st.dev/) - 600+ premium components
* [Pre-installed Components](/customisation/installed-components) - What's included
* [Theme Customization](/customisation/theme-guide) - Style your components
# Pre-installed Components (/customisation/installed-components)
import { ImageZoom } from 'fumadocs-ui/components/image-zoom';
import { Cards, Card } from 'fumadocs-ui/components/card';
# Pre-installed Components ๐จ
Indie Kit comes with beautiful, production-ready components that you can use right away! No installation needed - just import and use. โจ
All these components are already installed in your Indie Kit project. Simply import them and start building!
## What's Included? ๐ฆ
Your Indie Kit installation includes professionally designed components for:
* ๐ **Hero Sections** - Attention-grabbing landing page headers
* ๐ข **Call to Actions** - Conversion-focused CTAs
* ๐ซ **Feature Displays** - Showcase your product features
* ๐ฌ **Testimonials** - Build trust with social proof
* ๐ **Footer** - Clean, minimal footer design
## Hero Sections ๐
Hero sections are the first thing visitors see on your website. Choose from these options:
`import Hero1 from "@/components/sections/hero-1";`
`import Hero2 from "@/components/sections/hero-2";`
## Call to Action (CTA) ๐ข
Effective CTAs to drive user engagement:
`import CTA1 from "@/components/website/cta-1";`
`import { CTA2 } from "@/components/website/cta-2";`
## Feature Displays ๐ซ
Showcase your product features:
`import FeatureGrid from "@/components/sections/feature-grid";`
`import FeatureAccordion from "@/components/sections/feature-accordion";`
## Testimonials ๐ฌ
Display social proof with these testimonial layouts:
`import TestimonialGrid from "@/components/sections/testimonial-grid";`
`import Testimonial1 from "@/components/sections/testimonial-1";`
`import Testimonial2 from "@/components/sections/testimonial-2";`
## Footer ๐
Minimal footer design for a clean look:
## How to Use Components ๐
Using these components is simple! Here's a quick example:
### Basic Usage
```tsx
import Hero1 from "@/components/sections/hero-1";
export default function HomePage() {
return (
);
}
```
### Customizing Components
All components accept props for customization:
```tsx
import TestimonialGrid from "@/components/sections/testimonial-grid";
export default function AboutPage() {
const testimonials = [
{
name: "John Doe",
role: "CEO at Company",
content: "This product is amazing!",
avatar: "/images/john.jpg"
},
// ... more testimonials
];
return (
);
}
```
### Combining Components
Build complete pages by combining multiple components:
```tsx
import Hero1 from "@/components/sections/hero-1";
import FeatureGrid from "@/components/sections/feature-grid";
import TestimonialGrid from "@/components/sections/testimonial-grid";
import CTA1 from "@/components/website/cta-1";
export default function LandingPage() {
return (
);
}
```
Check each component's file to see available props and customization options. All components are fully typed with TypeScript!
## Component Features โจ
All pre-installed components include:
* โ **Tailwind CSS** - Easy to customize with utility classes
* โ **Fully Responsive** - Mobile, tablet, and desktop optimized
* โ **Dark Mode** - Automatically adapts to your theme
* โ **TypeScript** - Full type safety and autocomplete
* โ **Accessible** - Built following ARIA best practices
* โ **Customizable** - Pass data and customize behavior
* โ **Production Ready** - Tested and optimized
## Customization Guide ๐จ
### Modify Styles
Since components use Tailwind CSS, you can easily override styles:
```tsx
```
### Edit Component Code
Want deeper customization? Edit the component files directly:
```
src/
components/
sections/
hero-1.tsx โ Edit this file
feature-grid.tsx
testimonial-grid.tsx
website/
cta-1.tsx
cta-2.tsx
```
### Create Variants
Copy a component and create your own variant:
```bash
# Copy an existing component
cp src/components/sections/hero-1.tsx src/components/sections/hero-custom.tsx
```
Then modify `hero-custom.tsx` to match your needs!
## Best Practices ๐ก
1. **Start with Pre-built Components**: Use what's included before building custom ones
2. **Customize Gradually**: Start with props, then modify styles, then edit code if needed
3. **Keep Components Reusable**: Extract repeated patterns into new components
4. **Use TypeScript**: Take advantage of type safety for props
5. **Test Responsiveness**: Always check mobile, tablet, and desktop views
6. **Follow Naming Conventions**: Keep your custom components consistently named
## Component Locations ๐
All pre-installed components live in:
```
src/
components/
sections/ โ Landing page sections
hero-1.tsx
hero-2.tsx
feature-grid.tsx
feature-accordion.tsx
testimonial-grid.tsx
testimonial-1.tsx
testimonial-2.tsx
website/ โ Website components
cta-1.tsx
cta-2.tsx
minimal-footer.tsx
```
## Need More Components? ๐
Want access to 600+ additional components?
๐ Access 600+ shadcn/ui Components
These components cover everything from charts and data tables to advanced forms and animations!
## Next Steps ๐
Now that you know what components are available:
1. โ Browse through the components above
2. โ Copy the import statements and use them in your pages
3. โ Customize them to match your brand
4. โ Combine multiple components to build complete pages
5. โ Check out [Community Components](/customisation/components) for even more options
You have everything you need to build beautiful pages! Just import, customize, and ship! ๐จ
## Related Documentation ๐
* [Community Components](/customisation/components) - 600+ more components
* [Theme Customization](/customisation/theme-guide) - Customize colors
* [Static Pages](/customisation/static-page) - Build landing pages
# Protected Pages (/customisation/private-page)
YouTube Video: https://www.youtube.com/watch?v=sswHDbkGXQc
Protect your pages and API routes with built-in authentication! Multiple approaches for different scenarios. ๐ก๏ธ
Routes in `src/app/(in-app)/app/*` and `src/app/api/app/*` are automatically protected! No extra auth code needed for these paths.
## Three Protection Methods ๐ฏ
Choose the right approach for your use case:
| Method | Use For | Where |
| ----------------- | ---------------------------- | ----------------- |
| Server Components | Initial page load protection | Server components |
| Client Components | Dynamic UI with auth state | Client components |
| API Routes | Endpoint protection | API routes |
## Server-Side Protection ๐ฅ๏ธ
Use the `auth` helper for server components:
```tsx
// src/app/dashboard/page.tsx
import { auth } from '@/auth'
import { forbidden } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
forbidden() // Returns 403
}
return (
Welcome back, {session.user.name}! ๐
{/* Protected content */}
)
}
```
Add `authInterrupts: true` to `experimental` in `next.config.js` to use `forbidden()`.
**When to use:**
* โ Initial page load protection
* โ SEO-important authenticated pages
* โ Static user data display
* โ Better performance (no client hydration)
## Client-Side Protection ๐ป
Use the `useUser` hook for dynamic client components:
```tsx
'use client'
import useUser from '@/lib/users/useUser'
import { redirect } from 'next/navigation'
import { Skeleton } from '@/components/ui/skeleton'
export default function ProfilePage() {
const { user, isLoading } = useUser()
if (isLoading) {
return (
)
}
if (!user) {
redirect('/login')
}
return (
Your Profile โจ
Name: {user.name}
Email: {user.email}
{/* More profile content */}
)
}
```
**When to use:**
* โ Dynamic user interfaces
* โ Real-time data updates
* โ Interactive features
* โ Client-side state management
Always show a loading state to prevent flash of unauthenticated content. Use skeleton loaders for better UX!
## API Route Protection ๐
Protect API endpoints with `withAuthRequired` middleware:
```ts
// src/app/api/app/profile/route.ts
import withAuthRequired from '@/lib/auth/withAuthRequired'
import { NextResponse } from 'next/server'
export const GET = withAuthRequired(async (req, context) => {
const { session } = context
// Protected API logic
const userData = {
id: session.user.id,
name: session.user.name,
email: session.user.email,
}
return NextResponse.json(userData)
})
export const PATCH = withAuthRequired(async (req, context) => {
const { session } = context
const body = await req.json()
// Update user data
await updateUser(session.user.id, body)
return NextResponse.json({ success: true })
})
```
**Benefits:**
* โ Automatic session validation
* โ User data in context
* โ Type-safe session object
* โ Returns 401 if not authenticated
Never skip authentication on API routes that access user data! Client-side protection alone is not enough.
## Best Practices โ
**1. Double Protection**
```tsx
// โ Good - Protected on both client and server
'use client'
const { user } = useUser()
if (!user) redirect('/login')
// API route also protected
export const GET = withAuthRequired(...)
// โ Bad - Only client-side check
'use client'
const { user } = useUser()
if (!user) redirect('/login')
// API route unprotected โ ๏ธ
```
**2. Loading States**
```tsx
// โ Good - Smooth loading
if (isLoading) return
if (!user) redirect('/login')
// โ Bad - Flash of content
if (!user) redirect('/login')
```
**3. Error Handling**
```tsx
// โ Good - Clear error messages
if (!session) {
return NextResponse.json(
{ error: 'Please sign in to continue' },
{ status: 401 }
)
}
// โ Bad - Generic errors
if (!session) {
return NextResponse.json({ error: 'Error' }, { status: 401 })
}
```
**4. Redirect Appropriately**
```tsx
// โ Good - Redirect to login
if (!user) redirect('/login')
// โ Bad - Redirect to home (confusing)
if (!user) redirect('/')
```
## Quick Reference ๐
### Server Component
```tsx
import { auth } from '@/auth'
const session = await auth()
if (!session) forbidden()
```
### Client Component
```tsx
'use client'
import useUser from '@/lib/users/useUser'
const { user, isLoading } = useUser()
if (!user) redirect('/login')
```
### API Route
```tsx
import withAuthRequired from '@/lib/auth/withAuthRequired'
export const GET = withAuthRequired(async (req, context) => {
const { session } = context
// Your logic
})
```
You can now secure any page or API route! Always protect sensitive data on both client and server. ๐
## Related Documentation ๐
* [Password Authentication](/setup/auth/password-login) - Enable password login
* [Authentication Setup](/setup/auth) - Configure auth providers
* [API Calls](/customisation/api-calls) - Working with protected APIs
# Static Page (/customisation/static-page)
# Creating Static Pages ๐
Build SEO-optimized static pages with proper metadata, Open Graph tags, and structured data! Perfect for landing pages, about pages, and marketing content. ๐
Indie Kit includes automatic SEO optimization! Just add metadata and your page is search engine ready.
## Create Your Page ๐จ
### Step 1: Create Page File
Create a new file in `src/app/` directory, for example `src/app/events/join-us/page.tsx`:
```tsx
import { Metadata } from 'next'
import { WebPageJsonLd } from 'next-seo'
import { appConfig } from '@/lib/config'
// SEO metadata
export const metadata: Metadata = {
title: 'Join Us This Weekend - Special Event',
description: 'Join our community for an unforgettable weekend of learning and networking!',
openGraph: {
title: 'Join Us This Weekend - Special Event',
description: 'Join our community for an unforgettable weekend of learning and networking!',
url: `${process.env.NEXT_PUBLIC_APP_URL}/events/join-us`,
siteName: appConfig.projectName,
type: 'website',
images: ['/og-event.png'],
},
twitter: {
card: 'summary_large_image',
title: 'Join Us This Weekend',
description: 'Join our community event!',
images: ['/og-event.png'],
},
}
export default function JoinUsPage() {
return (
<>
{/* JSON-LD structured data for SEO */}
{/* Page content */}
Join Us This Weekend! ๐
Connect with amazing people and create unforgettable memories! โจ
>
)
}
```
### Step 2: Add Pre-built Components
Combine pre-installed components for beautiful pages:
```tsx
import Hero1 from "@/components/sections/hero-1"
import FeatureGrid from "@/components/sections/feature-grid"
import TestimonialGrid from "@/components/sections/testimonial-grid"
import CTA1 from "@/components/website/cta-1"
export default function LandingPage() {
return (
<>
>
)
}
```
All [pre-installed components](/customisation/installed-components) work together seamlessly! Build your perfect landing page in minutes.
## SEO Features Built-In โก
Indie Kit automatically handles:
* ๐ **Metadata** - Title, description, keywords
* ๐ผ๏ธ **Open Graph** - Social media previews
* ๐ฆ **Twitter Cards** - Twitter-specific previews
* ๐ **JSON-LD** - Structured data for search engines
* ๐บ๏ธ **Sitemap** - Auto-generated from pages
* ๐ค **Robots.txt** - Search engine instructions
### Metadata Configuration
```tsx
export const metadata: Metadata = {
title: 'Your Page Title',
description: 'Compelling description under 160 characters',
keywords: ['saas', 'indie', 'startup'],
openGraph: {
title: 'Your Page Title',
description: 'Same or different description',
images: ['/og-image.png'],
url: 'https://yourapp.com/page',
},
twitter: {
card: 'summary_large_image',
title: 'Your Page Title',
description: 'Twitter-specific description',
images: ['/twitter-og.png'],
},
}
```
### JSON-LD Structured Data
```tsx
```
JSON-LD helps search engines understand your content. Include it on important pages for better SEO!
## Best Practices โ
**1. Descriptive Metadata**
```tsx
// โ Good - Specific and compelling
title: "Ship Your SaaS in Days with Indie Kit"
description: "Production-ready Next.js boilerplate with auth, payments, and more. Launch your startup faster."
// โ Bad - Generic
title: "Home"
description: "Welcome to our website"
```
**2. Optimize Images**
```tsx
import Image from 'next/image'
// โ Good - Next.js Image with alt
// โ Bad - Regular img without alt
```
**3. Semantic HTML**
```tsx
// โ Good - Proper structure
Main Heading
Content
// โ Bad - All divs
Heading
Content
```
**4. Mobile Responsive**
```tsx
// โ Good - Responsive classes
Responsive Heading
// โ Bad - Fixed size
Too Big on Mobile
```
## Page Templates ๐
### Marketing Landing Page
```tsx
import { Metadata } from 'next'
import Hero2 from "@/components/sections/hero-2"
import FeatureGrid from "@/components/sections/feature-grid"
import CTA1 from "@/components/website/cta-1"
export const metadata: Metadata = {
title: 'Product Name - Solve X Problem',
description: 'Help customers achieve Y goal in Z time',
}
export default function ProductPage() {
return (
<>
>
)
}
```
### About Page
```tsx
export const metadata: Metadata = {
title: 'About Us - Our Mission',
description: 'Learn about our story and what drives us',
}
export default function AboutPage() {
return (
About Us
{/* Your content */}
)
}
```
### Pricing Page
```tsx
import MonthlyAnnualPricing from "@/components/website/monthly-annual-pricing"
export const metadata: Metadata = {
title: 'Pricing - Simple, Transparent Plans',
description: 'Choose the perfect plan for your needs. Start free!',
}
export default function PricingPage() {
return (
Simple Pricing
Choose the perfect plan for your needs
)
}
```
## SEO Checklist ๐
Before launching your page:
* โ **Unique title** - Different from other pages
* โ **Meta description** - 150-160 characters
* โ **Open Graph image** - 1200x630px recommended
* โ **JSON-LD data** - For rich search results
* โ **Alt text on images** - Describe every image
* โ **Semantic HTML** - Use proper tags (h1, main, section)
* โ **Mobile responsive** - Test on all devices
* โ **Fast loading** - Optimize images and code
* โ **Internal links** - Link to related pages
* โ **External links** - Add `rel="noopener"` for security
Use [Google's Rich Results Test](https://search.google.com/test/rich-results) to verify your structured data works correctly!
## Next Steps ๐
1. **Create your pages** with proper metadata
2. **Use pre-built components** for quick assembly
3. **Add structured data** for better SEO
4. **Test on mobile** to ensure responsiveness
5. **Check search preview** using SEO tools
Your static pages are now search engine ready! Build more pages and watch your traffic grow. ๐
## Related Documentation ๐
* [Pre-installed Components](/customisation/installed-components) - Available components
* [SEO Guide](/seo/seo) - Complete SEO documentation
* [Google Search Console](/seo/google-search-console) - Track search performance
# Theme Guide (/customisation/theme-guide)
YouTube Video: https://www.youtube.com/watch?v=5lBSrSPhjw8
import { Cards, Card } from 'fumadocs-ui/components/card';
Customize your app's look in minutes! Use visual theme builders to create beautiful color schemes with automatic dark mode. ๐จ
Indie Kit uses CSS variables for theming. Change a few values and your entire app updates instantly!
## Visual Theme Builders ๐จ
Pick your favorite tool to design your theme:
**Recommended** - Most powerful theme editor
Alternative with simple interface
**Features:**
* ๐จ **1000+ color combinations** - Explore pre-made palettes
* ๐ **Automatic dark mode** - Light and dark variants generated
* ๐ฏ **Real-time preview** - See changes instantly
* ๐ฑ **Accessible colors** - Ensures proper contrast
* ๐ **One-click export** - Copy CSS and paste
Don't start from scratch! Pick a pre-made palette close to your brand, then tweak it to perfection.
## Apply Your Theme โก
### Step 1: Generate Theme
1. Open [TweakCN](https://tweakcn.com/editor/theme)
2. Pick a base color for your brand
3. Adjust accent, background, and foreground colors
4. Preview light and dark modes
5. Click "Export" to copy CSS
### Step 2: Update CSS Variables
Open `src/app/globals.css` and replace the theme variables:
```css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--border: 214.3 31.8% 91.4%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
/* ... dark mode variables */
}
}
```
### Step 3: Save and See Changes
That's it! Your theme is live. ๐
All components automatically use these CSS variables. Change once, update everywhere!
## What You Can Customize ๐ฏ
### Colors
```css
--background /* Page background */
--foreground /* Text color */
--primary /* Brand color (buttons, links) */
--secondary /* Secondary elements */
--accent /* Highlights and accents */
--destructive /* Delete, error states */
--muted /* Disabled states */
--border /* Borders and dividers */
```
### Border Radius
```css
--radius: 0.5rem; /* Default rounded corners */
--radius: 0; /* Sharp corners */
--radius: 1rem; /* Very rounded */
```
### Fonts
Update in `tailwind.config.ts`:
```ts
export default {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
heading: ['Poppins', 'sans-serif'],
},
},
},
}
```
## Pre-made Themes ๐
Try these popular themes:
### Modern Blue
```css
:root {
--primary: 221.2 83.2% 53.3%; /* Blue */
--secondary: 210 40% 96.1%;
}
```
### Elegant Purple
```css
:root {
--primary: 262.1 83.3% 57.8%; /* Purple */
--secondary: 270 40% 96.1%;
}
```
### Bold Orange
```css
:root {
--primary: 24.6 95% 53.1%; /* Orange */
--secondary: 33 40% 96.1%;
}
```
### Minimal Gray
```css
:root {
--primary: 224 71% 4%; /* Dark gray */
--secondary: 220 14.3% 95.9%;
}
```
## Dark Mode ๐
Dark mode is built-in! The theme builder generates both light and dark variants.
### How It Works
```css
/* Light mode (default) */
:root {
--background: 0 0% 100%; /* White */
}
/* Dark mode (automatic) */
.dark {
--background: 222.2 84% 4.9%; /* Dark */
}
```
Users can toggle with the theme switcher component included in Indie Kit!
All components automatically adapt to dark mode using CSS variables. No extra code needed!
## Advanced Customization ๐ง
### Custom Color Palette
Create your own color system:
```css
:root {
/* Brand colors */
--brand-blue: 221 83% 53%;
--brand-green: 142 76% 36%;
--brand-red: 0 84% 60%;
/* Use in components */
--primary: var(--brand-blue);
--success: var(--brand-green);
--destructive: var(--brand-red);
}
```
### Per-Component Styles
Override specific components:
```tsx
// Custom button with brand color
```
### Gradient Backgrounds
```css
:root {
--gradient-primary: linear-gradient(to right, #667eea 0%, #764ba2 100%);
}
```
```tsx
Gradient background
```
## Testing Your Theme ๐งช
After applying a theme:
1. **Check all pages** - Landing, app, admin
2. **Test dark mode** - Toggle and verify contrast
3. **Check accessibility** - Use browser dev tools
4. **Mobile testing** - Colors should work on small screens
5. **Print preview** - Ensure readability if printed
Make sure text has enough contrast with backgrounds! Use the theme builder's accessibility checker.
## Tips & Tricks ๐ก
**1. Start Simple**
* Choose your primary color
* Let the theme builder generate the rest
* Fine-tune after seeing it in your app
**2. Test Both Modes**
* Some colors work great in light but not dark
* Adjust dark mode separately if needed
* Check all UI states (hover, active, disabled)
**3. Brand Consistency**
```tsx
// Use your brand colors
--primary: /* Your brand color */
--secondary: /* Complementary color */
```
**4. Save Your Theme**
* Keep your CSS variables in a separate file
* Document custom colors
* Save hex codes for reference
## Troubleshooting ๐ง
**Colors not applying?**
* Check CSS is in `globals.css`
* Verify file is imported in root layout
* Clear browser cache
* Restart dev server
**Dark mode broken?**
* Ensure `.dark` class variables are set
* Check theme switcher is working
* Verify all color variables have dark variants
**Components look wrong?**
* Check Tailwind config includes color variables
* Verify you're using HSL format (not hex or RGB)
* Make sure no hardcoded colors override theme
Your custom theme is applied! All components now use your brand colors. ๐จโจ
## Related Documentation ๐
* [Pre-installed Components](/customisation/installed-components) - Components to style
* [Community Components](/customisation/components) - More components
* [shadcn/ui Themes](https://ui.shadcn.com/themes) - Explore pre-made themes
# Deployment Guide (/deployment)
YouTube Video: https://www.youtube.com/watch?v=61yI-Knl1D4
Deploy your Indie Kit app to production in minutes! Choose from multiple platforms based on your needs and budget. โจ
The video tutorial above walks you through the entire deployment process. Follow along for the easiest experience!
## Choose Your Platform ๐ฏ
### Sherpa.sh ๐
**Perfect for indie hackers!** Affordable, simple, unlimited projects.
Sherpa offers unlimited projects and new user discounts! Great for bootstrappers building multiple side projects. ๐ฐ
**Why Sherpa?**
* ๐ฐ **Affordable pricing** - Perfect for indie hackers
* ๐ **Unlimited projects** - Build as many apps as you want
* ๐ **New user discounts** - Special offers for new users
* ๐ค **Helpful creator** - Reach out via email/Discord
**Deployment steps:**
1. **Create account** โ [sherpa.sh](https://www.sherpa.sh/?via=Indie%20Kit)
2. **Connect GitHub** โ Authorize repository access
3. **Select repository** โ Choose your Indie Kit project
4. **Add environment variables** โ Copy from `.env.local`
5. **Deploy!** โ Click deploy and you're live! ๐
**Need help?** The creator is super helpful! Contact them via:
* ๐ง Email from their website
* ๐ฌ Discord community
### Vercel โก
**Most popular option!** Fast, easy, great DX.
**Deployment steps:**
1. **Push to GitHub** โ Commit and push your code
2. **Visit Vercel** โ [vercel.com](https://vercel.com)
3. **Click "Add New Project"** โ Import repository
4. **Select repository** โ Choose your Indie Kit project
5. **Add environment variables** โ Copy from `.env.local` (see below)
6. **Click "Deploy"** โ Done! ๐
Vercel auto-detects Next.js! No build configuration needed.
### Netlify ๐
**Great alternative!** Solid performance and features.
**Deployment steps:**
1. **Push to GitHub** โ Commit your code
2. **Visit Netlify** โ [netlify.com](https://netlify.com)
3. **Click "Add new site"** โ Connect to Git
4. **Select repository** โ Choose your project
5. **Add environment variables** โ Site settings โ Environment variables
6. **Deploy!** โ Netlify handles the rest
### Render ๐จ
**Developer-friendly!** Simple and reliable.
**Deployment steps:**
1. **Push to GitHub** โ Save your code
2. **Visit Render** โ [render.com](https://render.com)
3. **New Web Service** โ Connect repository
4. **Select repository** โ Choose your Indie Kit app
5. **Add environment variables** โ Environment tab
6. **Deploy!** โ Watch it build
## Environment Variables ๐
All platforms need these environment variables. Copy from your `.env.local`:
### Required Variables
**Database:**
```bash
DATABASE_URL=your_database_connection_string
```
**Authentication:**
```bash
NEXTAUTH_URL=https://your-domain.com
NEXTAUTH_SECRET=your_secret_key
```
**OAuth (Google):**
```bash
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
```
**Email (AWS SES):**
```bash
AWS_ACCESS_KEY_ID=your_aws_access_key
AWS_SECRET_ACCESS_KEY=your_aws_secret_key
AWS_SES_REGION=us-east-1
```
**Payments (Stripe):**
```bash
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
```
Make sure `NEXTAUTH_URL` matches your production domain! Don't use localhost.
### Optional Variables
**Background Jobs (Inngest):**
```bash
INNGEST_EVENT_KEY=your_inngest_event_key
INNGEST_SIGNING_KEY=your_inngest_signing_key
```
**Analytics:**
```bash
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
```
## Post-Deployment Checklist โ
After deploying, verify everything works:
### 1. Test Core Features
**Authentication:**
* โ Sign up with email
* โ Sign in with Google
* โ Password reset works
**Database:**
* โ Data persists correctly
* โ No connection errors
**Emails:**
* โ Welcome emails arrive
* โ Magic link emails work
* โ Check spam folder first!
**Payments:**
* โ Checkout flows work
* โ Webhooks receive events
* โ Test with Stripe test mode first!
### 2. Configure Webhooks
**Stripe webhooks:**
1. Go to Stripe Dashboard โ Webhooks
2. Add endpoint: `https://your-domain.com/api/webhooks/stripe`
3. Copy webhook secret
4. Add to environment variables: `STRIPE_WEBHOOK_SECRET`
**Inngest webhooks (if using background jobs):**
1. Go to Inngest Dashboard
2. Add production app URL
3. Copy signing key
4. Add to environment variables
### 3. Monitor & Debug
**Check logs:**
* Platform dashboard shows build/runtime logs
* Monitor for errors
* Set up error tracking (Sentry recommended)
**Performance:**
* Test page load speeds
* Check mobile responsiveness
* Verify all routes work
Test your app thoroughly on the production URL before sharing publicly!
## Troubleshooting ๐ง
**Build failing?**
* Check Node.js version (should be 18+)
* Verify all dependencies in `package.json`
* Review build logs for errors
**Environment variables not working?**
* Redeploy after adding new variables
* Check for typos in variable names
* Ensure no extra spaces or quotes
**Database connection errors?**
* Verify `DATABASE_URL` is correct
* Check if database allows production connections
* Add platform's IP to database allowlist if needed
**OAuth not working?**
* Update authorized redirect URIs in Google Console
* Add: `https://your-domain.com/api/auth/callback/google`
* Make sure `NEXTAUTH_URL` matches your domain
**Stripe webhooks failing?**
* Test webhook endpoint manually
* Verify webhook secret is correct
* Check if webhook events are enabled
## Best Practices ๐
**1. Use Different Stripe Keys**
```bash
# Development: Test mode
STRIPE_SECRET_KEY=sk_test_...
# Production: Live mode
STRIPE_SECRET_KEY=sk_live_...
```
**2. Secure Your Secrets**
* Never commit `.env.local` to Git
* Use different secrets for production
* Rotate secrets periodically
**3. Set Up Monitoring**
* Error tracking (Sentry, LogRocket)
* Uptime monitoring (Better Uptime)
* Analytics (Google Analytics, Plausible)
**4. Plan for Scale**
* Monitor database usage
* Optimize slow queries
* Consider CDN for assets
## Next Steps ๐
1. **Set up custom domain** - Add your domain in platform settings
2. **Configure SSL** - Usually automatic on all platforms
3. **Set up monitoring** - Track errors and performance
4. **Launch!** - Share your app with the world ๐
Congratulations! Your Indie Kit app is now in production. Time to get users! ๐
## Related Documentation ๐
* [Database Setup](/setup/database) - Configure your database
* [Stripe Setup](/setup/payments/stripe) - Payment configuration
* [Email Setup](/setup/email) - Email provider setup
# Inngest Deployment (/deployment/vercel)
YouTube Video: https://www.youtube.com/watch?v=NYbJ6EMY5rA
import { Cards, Card } from 'fumadocs-ui/components/card';
Create an Inngest account to get started.
Learn more about deploying on Vercel.
# Docs (/docs)
# Documentation System ๐
Indie Kit comes with a powerful, production-ready documentation system. Write MDX files, and everything else is handled automatically! โจ
## What You Get Out of the Box ๐
Your docs come with incredible features built-in:
* ๐ค **Automatic llms.txt** - AI-ready content for ChatGPT and other platforms
* ๐ **Built-in Search** - Lightning-fast fuzzy search
* ๐ผ๏ธ **Auto OG Images** - Social media cards for every page
* ๐ **Copy as Markdown** - One-click copy for sharing
* ๐บ๏ธ **Automatic Sitemap** - SEO handled automatically
* ๐จ **Icon Support** - Add icons to pages effortlessly
* ๐ฑ **Mobile Responsive** - Perfect on all devices
Built on [Fumadocs](https://fumadocs.vercel.app/) - a modern documentation framework optimized for performance and developer experience.
## AI Integration ๐ค
Your docs are automatically LLM-ready:
### llms.txt Generation
Every time you add content, `llms.txt` is automatically generated with:
* Structured content for AI consumption
* Proper formatting for ChatGPT, Claude, and other LLMs
* Optimized for context window efficiency
### Open in ChatGPT
Users can open any doc page directly in ChatGPT with one click! The **"Open in ChatGPT"** button:
* Shares the page content with proper context
* Works with Claude and other AI platforms
* Includes markdown formatting
### Copy as Markdown
The **"Copy as Markdown"** feature lets users:
* Copy entire pages with formatting intact
* Share in Slack, Discord, or anywhere
* Preserve code blocks and structure
Your docs are optimized for both humans AND AI! Users can chat with your docs using their favorite AI assistant.
## Adding Content ๐
Adding new documentation is super simple:
### Step 1: Create Your MDX File
Drop your `.mdx` file in `content/docs/`:
```
content/docs/
โโโ getting-started.mdx
โโโ your-new-page.mdx
โโโ advanced/
โโโ custom-feature.mdx
```
### Step 2: Add Frontmatter
Every page needs frontmatter:
```mdx
---
title: Your Page Title
description: A helpful description for SEO
icon: Rocket
---
# Your Content Here
Write your documentation in MDX format!
```
### Step 3: That's It! โจ
Your page is now:
* โ Searchable
* โ Has an OG image
* โ In the sitemap
* โ Available to AI
* โ Mobile responsive
No build steps, no config files! Just add your `.mdx` file and everything works automatically.
## Customization ๐จ
### Custom OG Images
Want to customize social media preview images?
Edit the OG image generator:
```
src/app/api/og/route.tsx
```
Modify the template to match your branding:
```tsx
// Customize colors, fonts, layout
{/* Your custom OG image template */}
```
### Icons
Add icons to any page using [Lucide icons](https://lucide.dev):
```mdx
---
title: My Page
icon: Rocket # Any Lucide icon name
---
```
Common icons:
* ๐ `Rocket` - Getting started
* ๐ `Book` - Documentation
* โ๏ธ `Settings` - Configuration
* ๐ `Lock` - Security
* ๐ณ `CreditCard` - Payments
### Navigation Structure
Control sidebar navigation in `meta.json`:
```json
{
"title": "Section Name",
"pages": ["page-1", "page-2"],
"icon": "Folder"
}
```
## Features in Detail โก
### Search Implementation
Fuzzy search is automatically enabled:
* Search across all content
* Keyboard shortcuts (`Cmd+K` or `Ctrl+K`)
* Instant results
* No configuration needed
### Automatic Sitemap
Your `sitemap.xml` is generated automatically:
* All documentation pages included
* Proper priority and change frequency
* Submitted to search engines
### Mobile Navigation
Responsive by default:
* Collapsible sidebar
* Touch-friendly navigation
* Optimized for small screens
Every page automatically gets meta tags, OG images, and proper semantic HTML for maximum search engine visibility!
## Best Practices ๐ก
### Writing Great Docs
1. **Clear Titles** - Descriptive and searchable
2. **Use Callouts** - Highlight important information
3. **Code Examples** - Show, don't just tell
4. **Short Sections** - Break up long content
5. **Emojis** - Make it friendly and scannable
### Organizing Content
```
content/docs/
โโโ getting-started.mdx # First steps
โโโ setup/ # Installation guides
โโโ features/ # Feature documentation
โโโ guides/ # How-to guides
โโโ api/ # API reference
```
### Using MDX Features
MDX supports React components:
```mdx
This is a callout component!
```
## Next Steps ๐
Now that you understand the docs system:
1. **Add Your Content** - Start writing in `content/docs/`
2. **Customize Branding** - Update OG images and colors
3. **Organize Navigation** - Structure your `meta.json` files
4. **Test Search** - Make sure content is discoverable
Your documentation system is production-ready! Just add content and watch the magic happen. โจ
# Analytics (/analytics)
# Analytics
Let's set up analytics in your Indie Kit application! ๐
# Create Email Sequence (/background-jobs/create-email-sequence)
# Create Email Sequence ๐ง
Build powerful automated email sequences! Perfect for onboarding new users, nurturing leads, and running drip campaigns. โจ
Make sure you have [email provider](/setup/email) and [background jobs](/setup/background-jobs) configured first.
## What Are Email Sequences? ๐ค
Email sequences are automated series of emails sent over time:
**Common types:**
* ๐ **Welcome sequences** - Onboard new users
* ๐ **Educational drips** - Teach product features
* ๐ฐ **Trial sequences** - Convert free users to paid
* ๐ **Re-engagement** - Win back inactive users
* ๐ **Course delivery** - Send lessons over time
**Benefits:**
* Automate user onboarding
* Increase engagement
* Improve conversions
* Save time on manual emails
* Personalize at scale
Create sequences once and they run automatically for every user! No manual work needed.
## Build Your First Sequence ๐ ๏ธ
### Step 1: Create Sequence Function
Create `src/lib/inngest/functions/welcome-sequence.ts`:
```ts
import { inngest } from "@/lib/inngest/client"
import { sendEmail } from "@/lib/email/send"
export const welcomeSequence = inngest.createFunction(
{ id: "welcome-sequence" },
{ event: "user/signup" },
async ({ event, step }) => {
const { email, name } = event.data
// Day 0: Welcome (immediate)
await step.run("send-welcome", async () => {
await sendEmail({
to: email,
template: "welcome",
data: { name }
})
})
// Day 1: Getting started tips
await step.sleep("wait-1-day", "1d")
await step.run("send-getting-started", async () => {
await sendEmail({
to: email,
template: "getting-started",
data: { name }
})
})
// Day 3: Feature highlights
await step.sleep("wait-2-more-days", "2d")
await step.run("send-features", async () => {
await sendEmail({
to: email,
template: "feature-highlights",
data: { name }
})
})
// Day 7: Upgrade offer
await step.sleep("wait-4-more-days", "4d")
await step.run("send-upgrade-offer", async () => {
await sendEmail({
to: email,
template: "upgrade-offer",
data: { name }
})
})
}
)
```
**What this does:**
* โ๏ธ Sends welcome email immediately
* โฐ Waits 1 day, sends getting started tips
* โฐ Waits 2 more days, sends feature highlights
* โฐ Waits 4 more days, sends upgrade offer
Use `1d` (day), `1h` (hour), `1m` (minute), or `1s` (second). Inngest handles the scheduling automatically!
### Step 2: Register Function
Add to `src/lib/inngest/functions/index.ts`:
```ts
import { welcomeSequence } from "./welcome-sequence"
export const functions = [
welcomeSequence,
// ... other functions
]
```
### Step 3: Trigger the Sequence
Send an event when user signs up:
```ts
import { inngest } from "@/lib/inngest/client"
// In your signup handler
export async function handleSignup(user) {
// ... create user in database
// Trigger welcome sequence
await inngest.send({
name: "user/signup",
data: {
email: user.email,
name: user.name,
userId: user.id,
},
})
}
```
That's it! The sequence starts automatically and runs over 7 days. Inngest handles all the timing and retries!
## More Sequence Examples ๐ก
### Trial Reminder Sequence
Convert trial users before they expire:
```ts
export const trialReminders = inngest.createFunction(
{ id: "trial-reminders" },
{ event: "subscription/trial.started" },
async ({ event, step }) => {
const { email, name, trialEndsAt } = event.data
// 3 days before expiry
await step.sleepUntil("3-days-before",
new Date(trialEndsAt - 3 * 24 * 60 * 60 * 1000)
)
await step.run("send-3-day-warning", async () => {
await sendEmail({
to: email,
template: "trial-ending-soon",
data: { name, daysLeft: 3 }
})
})
// Last day
await step.sleepUntil("last-day",
new Date(trialEndsAt - 24 * 60 * 60 * 1000)
)
await step.run("send-last-chance", async () => {
await sendEmail({
to: email,
template: "trial-last-day",
data: { name }
})
})
}
)
```
### Re-engagement Sequence
Win back inactive users:
```ts
export const reengagementSequence = inngest.createFunction(
{ id: "reengage-inactive-users" },
{ event: "user/inactive.detected" },
async ({ event, step }) => {
const { email, name, lastActiveAt } = event.data
// Email 1: We miss you
await step.run("send-we-miss-you", async () => {
await sendEmail({
to: email,
template: "we-miss-you",
data: { name, lastActiveAt }
})
})
// Wait 3 days
await step.sleep("wait-3-days", "3d")
// Email 2: New features
await step.run("send-whats-new", async () => {
await sendEmail({
to: email,
template: "whats-new",
data: { name }
})
})
// Wait 4 more days
await step.sleep("wait-4-days", "4d")
// Email 3: Special offer
await step.run("send-special-offer", async () => {
await sendEmail({
to: email,
template: "comeback-offer",
data: { name, discountCode: "COMEBACK20" }
})
})
}
)
```
### Product Update Sequence
Announce new features to all users:
```ts
export const productUpdateSequence = inngest.createFunction(
{ id: "product-update" },
{ event: "product/feature.launched" },
async ({ event, step }) => {
const { featureName, featureDescription } = event.data
// Get all active users
const users = await step.run("get-users", async () => {
return await getAllActiveUsers()
})
// Send to all users
await step.run("send-announcements", async () => {
for (const user of users) {
await sendEmail({
to: user.email,
template: "feature-announcement",
data: {
name: user.name,
featureName,
featureDescription
}
})
}
})
}
)
```
For large user lists, use Inngest's fan-out pattern to send emails in parallel and avoid timeouts!
## Advanced Features โก
### Conditional Logic
Send different emails based on user behavior:
```ts
export const smartOnboarding = inngest.createFunction(
{ id: "smart-onboarding" },
{ event: "user/signup" },
async ({ event, step }) => {
const { email, name, plan } = event.data
// Welcome email
await step.run("send-welcome", async () => {
await sendEmail({ to: email, template: "welcome" })
})
await step.sleep("wait-1-day", "1d")
// Different content based on plan
if (plan === "free") {
await step.run("send-upgrade-tips", async () => {
await sendEmail({
to: email,
template: "why-upgrade",
data: { name }
})
})
} else {
await step.run("send-pro-features", async () => {
await sendEmail({
to: email,
template: "pro-features",
data: { name }
})
})
}
}
)
```
### Stop Sequence Early
Cancel sequence if user takes action:
```ts
export const trialSequence = inngest.createFunction(
{ id: "trial-sequence" },
{ event: "trial/started" },
async ({ event, step }) => {
const { email, userId } = event.data
await step.sleep("wait-5-days", "5d")
// Check if user already upgraded
const user = await step.run("check-status", async () => {
return await getUser(userId)
})
if (user.isPaid) {
return { message: "User already upgraded, stopping sequence" }
}
// Send reminder only if still on trial
await step.run("send-reminder", async () => {
await sendEmail({
to: email,
template: "upgrade-reminder"
})
})
}
)
```
### Track Opens & Clicks
```ts
await step.run("send-with-tracking", async () => {
const result = await sendEmail({
to: email,
template: "welcome",
data: { name },
tracking: {
campaign: "welcome-sequence",
source: "inngest"
}
})
return result // Track delivery status
})
```
## Best Practices โ
**1. Timing Strategy**
```tsx
// โ Good - Natural spacing
Day 0: Welcome
Day 1: Getting started tips
Day 3: Feature highlights
Day 7: Upgrade offer
// โ Bad - Too aggressive
Day 0: Welcome
Day 0.5: Buy now!
Day 1: Last chance!
Day 1.5: Final offer!
```
**2. Email Content**
* Keep each email focused on one topic
* Include clear CTAs
* Make it easy to unsubscribe
* Personalize with user data
* Mobile-friendly design
**3. Error Handling**
```ts
// โ Good - Retry on failures
await step.run("send-email", async () => {
try {
await sendEmail({ to: email, template: "welcome" })
} catch (error) {
console.error("Failed to send:", error)
throw error // Inngest will retry
}
})
// โ Bad - Swallow errors
try {
await sendEmail(...)
} catch {
// Ignored
}
```
**4. Testing**
* Test locally with Inngest dev server
* Use your own email to verify timing
* Check all templates render correctly
* Monitor delivery rates
* Verify unsubscribe links work
## Sequence Ideas ๐ฏ
**Onboarding (First 30 days):**
* Day 0: Welcome ๐
* Day 1: Quick start guide
* Day 3: Feature showcase
* Day 7: Check-in & support offer
* Day 14: Success stories
* Day 30: Upgrade invitation
**Trial Conversion (14-day trial):**
* Day 0: Trial started
* Day 3: Feature deep dive
* Day 7: Midpoint check-in
* Day 11: Trial ending reminder
* Day 13: Last day warning
* Day 14: Trial expired (offer discount)
**Lead Nurturing:**
* Week 1: Problem awareness
* Week 2: Solution introduction
* Week 3: Social proof & case studies
* Week 4: Special offer
**Educational Course:**
* Send 1 lesson per day
* Include exercises
* Track completion
* Offer certification
Plan your sequence on paper first! Map out the user journey and what value each email provides.
## Monitoring & Analytics ๐
### View Sequence Status
Check Inngest dashboard:
1. Go to [app.inngest.com](https://app.inngest.com)
2. Click on your function
3. See all running sequences
4. Check completion rates
5. View any errors
### Track Performance
```ts
export const trackedSequence = inngest.createFunction(
{ id: "tracked-welcome" },
{ event: "user/signup" },
async ({ event, step }) => {
const startTime = Date.now()
// ... your sequence
await step.run("track-completion", async () => {
await analytics.track({
event: "sequence_completed",
userId: event.data.userId,
duration: Date.now() - startTime,
})
})
}
)
```
## Troubleshooting ๐ง
**Emails not sending?**
* Check email provider is configured
* Verify email templates exist
* Look at Inngest execution logs
* Test email function separately
**Timing issues?**
* Check sleep duration format
* Verify server timezone
* Look at Inngest dashboard for delays
* Test with shorter durations first
**Sequence not starting?**
* Check event name matches exactly
* Verify function is registered
* Look for errors in Inngest dashboard
* Check if event was sent successfully
**Users getting multiple sequences?**
* Check for duplicate event sends
* Use idempotency keys
* Verify function registration
* Check database for duplicate users
Use Inngest dev server at `localhost:8288` to test sequences with accelerated timing (seconds instead of days)!
## Next Steps ๐
1. **Create email templates** in `src/emails/`
2. **Build your first sequence** with the examples above
3. **Test locally** with dev server
4. **Monitor in production** via Inngest dashboard
5. **Iterate based on metrics** - Open rates, conversions
You can now automate your email marketing! Start with a simple welcome sequence and expand from there. ๐งโจ
## Related Documentation ๐
* [Background Jobs Setup](/setup/background-jobs) - Configure Inngest
* [Email Setup](/setup/email) - Configure email provider
* [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns
* [Inngest Docs](https://www.inngest.com/docs) - Advanced features
# Cron Jobs (/background-jobs/cron)
YouTube Video: https://www.youtube.com/watch?v=C5ag3lfL3BQ
Run tasks on a schedule! Clean up data daily, send weekly reports, or process monthly billing automatically. โฐ
Indie Kit supports Vercel Cron, Inngest scheduled functions, and external cron services. Pick what works best for your hosting!
## What Are Cron Jobs? ๐ค
Cron jobs are tasks that run on a schedule:
**Common use cases:**
* ๐งน **Daily cleanup** - Delete old data, expire sessions
* ๐ **Weekly reports** - Send analytics summaries
* ๐ณ **Monthly billing** - Process subscriptions
* ๐ **Hourly sync** - Update external data
* ๐ง **Email digests** - Send daily/weekly digests
## Using Inngest (Recommended) โก
Inngest provides the easiest way to schedule jobs:
### Create Scheduled Function
```ts
// src/lib/inngest/functions/daily-cleanup.ts
import { inngest } from "@/lib/inngest/client"
export const dailyCleanup = inngest.createFunction(
{ id: "daily-cleanup" },
{ cron: "0 0 * * *" }, // Every day at midnight
async ({ step }) => {
await step.run("delete-old-sessions", async () => {
await deleteExpiredSessions()
})
await step.run("cleanup-temp-files", async () => {
await cleanupTempFiles()
})
return { cleaned: true }
}
)
```
**Register it:**
```ts
// src/lib/inngest/functions/index.ts
import { dailyCleanup } from "./daily-cleanup"
export const functions = [
dailyCleanup,
// ... other functions
]
```
That's it! Inngest handles the scheduling automatically. No deployment config needed!
### Cron Pattern Examples
```ts
// Every minute
{ cron: "* * * * *" }
// Every hour
{ cron: "0 * * * *" }
// Every day at midnight
{ cron: "0 0 * * *" }
// Every day at 9 AM
{ cron: "0 9 * * *" }
// Every Monday at 9 AM
{ cron: "0 9 * * 1" }
// First day of month at midnight
{ cron: "0 0 1 * *" }
// Every weekday at 6 PM
{ cron: "0 18 * * 1-5" }
```
Format: `minute hour day month weekday` - Use [Crontab Guru](https://crontab.guru/) to build complex patterns!
## Using Vercel Cron ๐ข
For apps deployed on Vercel:
### Step 1: Create API Route
```ts
// src/app/api/cron/daily-cleanup/route.ts
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
// Verify cron secret
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Run cleanup
await deleteExpiredSessions()
await cleanupTempFiles()
return NextResponse.json({ success: true })
}
```
### Step 2: Add to vercel.json
```json
{
"crons": [
{
"path": "/api/cron/daily-cleanup",
"schedule": "0 0 * * *"
}
]
}
```
### Step 3: Set Environment Variable
```bash
CRON_SECRET="your-random-secret-key"
```
Vercel Cron only works on Vercel hosting! Use Inngest for platform-agnostic scheduling.
## Using External Cron Service ๐
For any hosting platform, use [cron-job.org](https://cron-job.org):
### Step 1: Create Protected API Route
```ts
// src/app/api/cron/cleanup/route.ts
export async function GET(request: Request) {
// Basic auth protection
const authHeader = request.headers.get('authorization')
const [username, password] = Buffer.from(
authHeader?.split(' ')[1] || '',
'base64'
).toString().split(':')
if (
username !== process.env.CRON_USERNAME ||
password !== process.env.CRON_PASSWORD
) {
return new Response('Unauthorized', { status: 401 })
}
// Run your task
await performCleanup()
return Response.json({ success: true })
}
```
### Step 2: Setup on cron-job.org
1. Create free account
2. Add new cron job
3. **URL:** `https://yourapp.com/api/cron/cleanup`
4. **Schedule:** Choose timing (daily, weekly, etc.)
5. **Authentication:** Add HTTP Basic Auth with credentials
6. **Save** and activate
### Step 3: Add Credentials to .env
```bash
CRON_USERNAME="secure-username"
CRON_PASSWORD="secure-strong-password"
```
Works with any hosting platform! Great for Render, Railway, or custom servers.
## Common Scheduled Tasks ๐
### Daily Cleanup
```ts
export const dailyCleanup = inngest.createFunction(
{ id: "daily-cleanup" },
{ cron: "0 2 * * *" }, // 2 AM daily
async ({ step }) => {
// Delete expired sessions
await step.run("sessions", async () => {
await db.delete(sessions)
.where(lt(sessions.expiresAt, new Date()))
})
// Delete old logs
await step.run("logs", async () => {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
await db.delete(logs)
.where(lt(logs.createdAt, thirtyDaysAgo))
})
}
)
```
### Weekly Reports
```ts
export const weeklyReport = inngest.createFunction(
{ id: "weekly-report" },
{ cron: "0 9 * * 1" }, // Mondays at 9 AM
async ({ step }) => {
const stats = await step.run("get-stats", async () => {
return await getWeeklyStats()
})
await step.run("send-report", async () => {
await sendEmail({
to: "admin@yourapp.com",
template: "weekly-report",
data: stats
})
})
}
)
```
### Monthly Billing
```ts
export const monthlyBilling = inngest.createFunction(
{ id: "monthly-billing" },
{ cron: "0 0 1 * *" }, // First day of month
async ({ step }) => {
const users = await step.run("get-users", async () => {
return await getActiveSubscriptions()
})
await step.run("process-billing", async () => {
for (const user of users) {
await processMonthlyCharge(user)
}
})
}
)
```
## Best Practices โ
**1. Use UTC Timezone**
* All cron times are in UTC
* Convert local time to UTC
* Document timezone in comments
**2. Idempotency**
```ts
// โ Good - Can run multiple times safely
await deleteExpiredSessions() // Safe to run multiple times
// โ Bad - Duplicate data on re-run
await sendEmailToEveryone() // Might send duplicates
```
**3. Error Handling**
```ts
await step.run("task", async () => {
try {
await riskyOperation()
} catch (error) {
console.error("Task failed:", error)
throw error // Trigger retry
}
})
```
**4. Monitor Execution**
* Set up alerts for failures
* Check execution logs regularly
* Monitor duration and performance
* Track success rates
## Testing Cron Jobs ๐งช
### Test Locally
```ts
// Call your cron function directly
import { dailyCleanup } from "@/lib/inngest/functions/daily-cleanup"
// In test file or API route
const result = await dailyCleanup.handler({
event: { name: "cron", data: {} },
// ... mock context
})
```
### Test Timing
Use shorter intervals for testing:
```ts
// Production
{ cron: "0 0 * * *" } // Daily
// Testing
{ cron: "*/5 * * * *" } // Every 5 minutes
```
Change back before deploying!
Always test cron jobs in development with shorter intervals before deploying to production!
## Troubleshooting ๐ง
**Job not running?**
* Verify cron pattern is correct
* Check function is registered
* Look at Inngest/Vercel logs
* Ensure environment variables set
**Running multiple times?**
* Check for duplicate registrations
* Verify cron pattern
* Look for multiple deployments
* Check Inngest dashboard
**Timing off?**
* Remember UTC timezone
* Convert from local time
* Check server timezone settings
* Test pattern on crontab.guru
## Next Steps ๐
1. **Create your first cron job** - Start with daily cleanup
2. **Test locally** - Use Inngest dev server
3. **Deploy** - Push and verify in dashboard
4. **Monitor** - Set up alerts for failures
5. **Optimize** - Adjust timing based on needs
Your cron jobs are ready to run! Automate repetitive tasks and save yourself time. โฐ
## Related Documentation ๐
* [Background Jobs Setup](/setup/background-jobs) - Configure Inngest
* [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - More examples
* [Email Sequences](/background-jobs/create-email-sequence) - Automated emails
* [Inngest Cron Docs](https://www.inngest.com/docs/guides/scheduled-functions) - Advanced scheduling
# Running Scheduled Jobs (/background-jobs/running-scheduled-jobs)
YouTube Video: https://www.youtube.com/watch?v=gz2331T2sAw
Run reliable background jobs! Execute tasks on events, schedules, or delays with automatic retries and monitoring. โก
Background jobs handle async tasks like sending emails, processing data, generating reports, and cleaning up databases!
## Event-Driven Jobs ๐ฏ
Jobs that run when events happen in your app.
### Create Your First Job
Create `src/lib/inngest/functions/hello-world.ts`:
```ts
import { inngest } from "@/lib/inngest/client"
export const helloWorld = inngest.createFunction(
{ id: "hello-world" },
{ event: "test/hello.world" },
async ({ event, step }) => {
// Wait 1 minute
await step.sleep("wait-a-bit", "1m")
// Do something
await step.run("say-hello", async () => {
console.log("Hello, World!", event.data)
return { message: "Hello, World!" }
})
}
)
```
**Register it in** `src/lib/inngest/functions/index.ts`:
```ts
import { helloWorld } from "./hello-world"
export const functions = [
helloWorld,
// ... other functions
]
```
Inngest automatically discovers and registers your functions. No manual setup needed!
### Trigger Jobs with Events
Send events to trigger your functions:
```ts
import { inngest } from "@/lib/inngest/client"
// Trigger background job
await inngest.send({
name: "test/hello.world",
data: {
message: "Test data",
userId: "123",
},
})
```
**Real-world example:**
```ts
// src/app/api/checkout/route.ts
export async function POST(req: Request) {
const order = await createOrder(await req.json())
// Trigger background job to process order
await inngest.send({
name: "order/created",
data: {
orderId: order.id,
userId: order.userId,
total: order.total,
items: order.items,
},
})
return Response.json({ success: true })
}
```
Events are sent asynchronously! Your API responds immediately while the job processes in the background.
## Scheduled Jobs (Cron) โฐ
Run jobs on a recurring schedule:
```ts
// src/lib/inngest/functions/daily-report.ts
export const dailyReport = inngest.createFunction(
{ id: "daily-report" },
{ cron: "0 9 * * *" }, // Every day at 9 AM
async ({ step }) => {
const stats = await step.run("get-stats", async () => {
return await getDailyStats()
})
await step.run("send-email", async () => {
await sendEmail({
to: "admin@yourapp.com",
template: "daily-report",
data: stats
})
})
}
)
```
**Common schedules:**
```ts
{ cron: "0 0 * * *" } // Daily at midnight
{ cron: "0 */6 * * *" } // Every 6 hours
{ cron: "0 9 * * 1" } // Mondays at 9 AM
{ cron: "0 0 1 * *" } // First of month
```
## Monitoring Jobs ๐
### Local Development
Visit `http://localhost:8288` to:
* ๐ See all registered functions
* ๐ View running jobs
* ๐ Check execution logs
* ๐ Replay failed jobs
* ๐ Debug step-by-step
### Production
Use [Inngest Cloud Dashboard](https://app.inngest.com):
* ๐ Real-time execution monitoring
* ๐จ Set up failure alerts
* ๐ View performance metrics
* ๐ Search execution history
* ๐ Retry failed jobs manually
Inngest provides complete visibility into your background jobs. No extra monitoring tools needed!
## Advanced Patterns ๐
### Step Functions
Break jobs into retryable steps:
```ts
export const complexJob = inngest.createFunction(
{ id: "complex-job" },
{ event: "job/complex" },
async ({ event, step }) => {
// Step 1: Fetch data (retries if fails)
const data = await step.run("fetch-data", async () => {
return await fetchExternalData()
})
// Step 2: Process (retries independently)
const processed = await step.run("process", async () => {
return await processData(data)
})
// Step 3: Save (retries independently)
await step.run("save", async () => {
await saveToDatabase(processed)
})
}
)
```
**Benefits:**
* โ Each step retries independently
* โ Resume from failed step
* โ No duplicate work
* โ Better debugging
### Parallel Execution
Process items in parallel:
```ts
export const processUsers = inngest.createFunction(
{ id: "process-users" },
{ event: "users/process.batch" },
async ({ event, step }) => {
const users = event.data.users
// Process all users in parallel
await Promise.all(
users.map((user) =>
step.run(`process-${user.id}`, async () => {
await processUser(user)
})
)
)
}
)
```
### Conditional Execution
Run steps based on conditions:
```ts
export const conditionalJob = inngest.createFunction(
{ id: "conditional-job" },
{ event: "user/action" },
async ({ event, step }) => {
const user = await step.run("get-user", async () => {
return await getUser(event.data.userId)
})
// Only send email if user is premium
if (user.isPremium) {
await step.run("send-premium-email", async () => {
await sendEmail({
to: user.email,
template: "premium-content"
})
})
} else {
await step.run("send-upgrade-prompt", async () => {
await sendEmail({
to: user.email,
template: "upgrade-now"
})
})
}
}
)
```
## Best Practices โ
**1. Use Step Functions**
```ts
// โ Good - Retryable steps
await step.run("fetch", async () => fetch())
await step.run("process", async () => process())
// โ Bad - All or nothing
await fetch()
await process()
```
**2. Name Things Clearly**
```ts
// โ Good - Descriptive
{ id: "send-welcome-email-sequence" }
{ event: "user/signup.completed" }
// โ Bad - Vague
{ id: "job1" }
{ event: "event" }
```
**3. Handle Errors**
```ts
await step.run("risky-operation", async () => {
try {
await riskyOperation()
} catch (error) {
console.error("Failed:", error)
throw error // Triggers automatic retry
}
})
```
**4. Keep Jobs Idempotent**
* Jobs might run multiple times
* Use unique IDs to prevent duplicates
* Check state before performing actions
* Make operations safe to retry
## Common Use Cases ๐ฏ
**User Actions:**
* ๐ง Send welcome emails
* ๐ Trigger onboarding sequences
* ๐ Track user activity
* ๐ Give signup bonuses
**Scheduled Maintenance:**
* ๐งน Daily data cleanup
* ๐ Weekly analytics reports
* ๐ณ Monthly billing processing
* ๐ Hourly data syncing
**Processing:**
* ๐ผ๏ธ Image optimization
* ๐ PDF generation
* ๐น Video transcoding
* ๐ Search index updates
Retries, delays, parallelization, and monitoring are all built-in. Focus on your business logic!
## Troubleshooting ๐ง
**Function not appearing?**
* Check it's exported from `functions/index.ts`
* Restart dev server
* Look at Inngest dashboard
* Verify function syntax
**Event not triggering job?**
* Check event name matches exactly
* Verify event was sent successfully
* Look at Inngest event stream
* Test with Inngest dev server
**Job failing?**
* Check error logs in dashboard
* Verify all dependencies available
* Test step-by-step locally
* Check environment variables
**Steps taking too long?**
* Optimize slow operations
* Break into smaller steps
* Use parallel execution
* Check for infinite loops
## Next Steps ๐
1. **Create your first job** - Start simple with hello world
2. **Send test event** - Trigger it and watch execution
3. **Add error handling** - Make it production-ready
4. **Monitor execution** - Check logs and timing
5. **Build real features** - Email sequences, reports, cleanup
You can now build reliable async features! Start with a simple job and expand from there. โก
## Related Documentation ๐
* [Background Jobs Setup](/setup/background-jobs) - Configure Inngest
* [Email Sequences](/background-jobs/create-email-sequence) - Automated emails
* [Cron Jobs](/background-jobs/cron) - Scheduled tasks
* [Inngest Docs](https://www.inngest.com/docs) - Full platform documentation
# IDE Setup (/codebase/ide-setup)
import { Tabs, Tab } from 'fumadocs-ui/components/tabs'
# IDE Setup ๐ป
Set up your development environment with AI-powered editors for maximum productivity! Code faster and smarter. โจ
AI code editors can 3-10x your coding speed! We highly recommend Cursor or Windsurf.
## Setting up Cursor โญ
**Best AI coding experience!**
### Why Cursor?
* ๐ค **AI that understands your project** - Chat with your entire codebase
* โก **AI autocomplete** - Write code 10x faster
* ๐ง **Context-aware** - AI knows your project structure
* ๐ง **VS Code compatible** - All extensions work perfectly
### Installation Steps
1. **Download Cursor** ๐ฅ
๐ [Download Cursor](https://cursor.sh)
2. **Install the app** ๐ป
* Open the downloaded file
* Follow installation prompts
* Launch Cursor
3. **Open Indie Kit project** ๐
* Click "Open Folder"
* Select your Indie Kit project directory
* Click "Select Folder"
4. **Install extensions** ๐
* Cursor will detect recommended extensions
* Click "Install All" when prompted
* Done! ๐
* `Cmd+K` (Mac) / `Ctrl+K` (Win) - AI command
* `Cmd+L` (Mac) / `Ctrl+L` (Win) - Chat with codebase
### Using Cursor AI ๐ค
**Ask questions:**
* "How does authentication work in this project?"
* "Create a new API route for user profile"
* "Fix this TypeScript error"
**AI autocomplete:**
Just start typing - AI suggests complete code blocks!
**Chat with codebase:**
Open chat (`Cmd+L`) and ask anything about your project.
## Setting up Windsurf ๐
**Another excellent AI editor!**
### Why Windsurf?
* ๐ค **AI-powered coding** - Smart code suggestions
* ๐ **Fast and lightweight** - Quick startup
* ๐ก **Intelligent assistance** - Context-aware help
* ๐ง **VS Code compatible** - Extensions work here too
### Installation Steps
1. **Download Windsurf** ๐ฅ
๐ [Download Windsurf](https://codeium.com/windsurf)
2. **Install the app** ๐ป
* Open the downloaded installer
* Complete the installation
* Launch Windsurf
3. **Open Indie Kit project** ๐
* Click "Open Folder"
* Navigate to your Indie Kit directory
* Click "Select Folder"
4. **Install extensions** ๐
* Look for notification about recommended extensions
* Click "Install All"
* All set! โ
Windsurf is a powerful AI code editor worth trying if Cursor doesn't fit your workflow!
### Using Windsurf AI ๐ค
Similar to Cursor, use AI features to:
* ๐ง Generate code
* ๐ Debug issues
* ๐ Understand patterns
* โป๏ธ Refactor code
## Setting up VS Code ๐
**Traditional editor, still great!**
### Installation Steps
1. **Download VS Code** ๐ฅ
๐ [Download VS Code](https://code.visualstudio.com)
2. **Install the app** ๐ป
* Open the downloaded installer
* Complete installation
* Launch VS Code
3. **Open Indie Kit project** ๐
* Click "Open Folder"
* Select your Indie Kit directory
* Click "Select Folder"
4. **Install extensions** ๐
* VS Code will suggest recommended extensions
* Click "Install All"
* Done! ๐
Install AI extensions to supercharge VS Code:
* **GitHub Copilot** - AI code completion
* **Codeium** - Free AI assistant
* **Tabnine** - AI autocomplete
### Recommended AI Extensions
**GitHub Copilot** (Paid)
๐ [Install GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot)
**Codeium** (Free)
๐ [Install Codeium](https://marketplace.visualstudio.com/items?itemName=Codeium.codeium)
## Install Extensions ๐
These extensions make Indie Kit development much easier!
### Automatic Installation (Easy Way) โจ
1. **Open your editor** in the Indie Kit project folder
2. **Look for notification** - "Do you want to install recommended extensions?"
3. **Click "Install All"** - Done! ๐
Your editor will automatically detect the recommended extensions from the project.
After installing extensions, restart your editor to activate them all!
### Recommended Extensions ๐ฆ
#### Essential Extensions
**Tailwind CSS IntelliSense** - CSS autocomplete โจ
๐ [Install Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
**ESLint** - Code quality ๐
๐ [Install ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
**Prettier** - Code formatting ๐จ
๐ [Install Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
**TypeScript Latest** - TypeScript support ๐
๐ [Install TypeScript Latest](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next)
**ES7+ React Snippets** - React shortcuts โ๏ธ
๐ [Install ES7+ React Snippets](https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets)
#### Helpful Extensions
**Tailwind Snippets** - Quick Tailwind classes ๐ฏ
๐ [Install Tailwind Snippets](https://marketplace.visualstudio.com/items?itemName=zarifprogrammer.tailwind-snippets)
**Simple React Snippets** - More React shortcuts ๐
๐ [Install Simple React Snippets](https://marketplace.visualstudio.com/items?itemName=burkeholland.simple-react-snippets)
**npm Intellisense** - Package imports ๐ฆ
๐ [Install npm Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense)
**Better Comments** - Colorful comments ๐ฌ
๐ [Install Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments)
**Import Cost** - See package sizes ๐ฐ
๐ [Install Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost)
### Manual Installation ๐ง
If automatic installation doesn't work:
1. Press `Cmd+Shift+X` (Mac) or `Ctrl+Shift+X` (Windows)
2. Click the extension link above
3. Click "Install" in your browser
4. Repeat for each extension
## Verify Your Setup โ
After setup, test that everything works:
**1. Check Tailwind IntelliSense**
```tsx
// Type "flex" - you should see autocomplete!
```
**2. Check ESLint**
Open any `.ts` or `.tsx` file - errors should show red underlines.
**3. Check Prettier**
Save any file - it should auto-format!
**4. Check TypeScript**
Hover over any variable - you should see type information.
If you see autocomplete, linting, and formatting - you're all set! ๐
## Troubleshooting ๐ง
**Extensions not appearing?**
* Check you're in the project folder
* Restart your editor
* Look for notification about recommended extensions
**Tailwind autocomplete not working?**
* Install "Tailwind CSS IntelliSense" extension
* Restart editor
* Verify `tailwind.config.ts` exists
**ESLint errors everywhere?**
* Run `pnpm install` to install dependencies
* Restart editor
* Wait a few seconds for ESLint to initialize
**Prettier not formatting?**
* Install Prettier extension
* Set Prettier as default formatter
* Enable "Format on Save" in editor settings
**AI features not working? (Cursor/Windsurf)**
* Check you're logged into the AI service
* Verify internet connection
* Restart the editor
## Next Steps ๐
1. **Start coding** - Open `src/app/page.tsx`
2. **Test AI features** - Try Cursor (`Cmd+K`) or Windsurf AI
3. **Explore codebase** - Use AI to understand structure
4. **Build your app** - Start customizing!
Your IDE is set up with AI superpowers! Start building. ๐ช
## Related Documentation ๐
* [Formatting & Linting](/codebase/formatting-and-linting) - Code quality tools
* [Keeping Things Updated](/codebase/keeping-things-updated) - Update your project
* [Getting Started](/getting-started) - Installation guide
# Keeping Your Project Updated (/codebase/keeping-things-updated)
YouTube Video: https://www.youtube.com/watch?v=LLx143cDyhs
Stay in sync with the latest Indie Kit features, fixes, and improvements! Get new updates without losing your customizations. โจ
The video tutorial above walks you through the entire process. Follow along for the easiest experience!
## Quick Update Process ๐
### Step 1: Add Upstream Remote
Add Indie Kit as a remote source (one-time setup):
**For Indie Kit:**
```bash
git remote add ik https://github.com/indie-kit/indie-kit
```
**For B2B Kit:**
```bash
git remote add ik https://github.com/indie-kit/b2b-boilerplate
```
You only need to do this once! Skip to Step 2 if you've already added the remote.
### Step 2: Configure Git
Set merge strategy for pulls:
```bash
git config pull.rebase false
```
**What this does:** Tells git to merge updates instead of rebasing. Safer for keeping your changes!
### Step 3: Pull Latest Updates
Get the latest changes:
**For Indie Kit:**
```bash
git pull ik main
```
**For B2B Kit:**
```bash
git pull ik b2b
```
๐ฏ Git will automatically merge new features into your project!
### Step 4: Handle Conflicts
If you get merge conflicts, don't panic! ๐
**What are conflicts?**
Git doesn't know which version to keep when you and Indie Kit both changed the same code.
**How to fix:**
1. Open conflicted files in VS Code
2. You'll see conflict markers: `<<<<<<<`, `=======`, `>>>>>>>`
3. Choose which version to keep (yours, theirs, or both)
4. Delete the conflict markers
5. Save the file
Check the video tutorial above for visual examples of resolving conflicts!
### Step 5: Commit & Push
Finalize the update:
```bash
git add .
git commit -m "Merge latest Indie Kit updates"
git push
```
Done! ๐ Your project is now up to date!
## What Gets Updated? ๐
When you pull updates, you'll get:
* โจ **New features** - Latest components and functionality
* ๐ **Bug fixes** - Stability improvements
* ๐ **Security patches** - Important security updates
* ๐ **Documentation** - Updated guides
* โก **Performance** - Speed optimizations
Your customizations stay intact! Only core Indie Kit files get updated.
## Best Practices โ
**1. Update Regularly**
```bash
# Check for updates weekly
git pull ik main # or 'ik b2b' for B2B Kit
```
**2. Commit Before Updating**
Always commit your changes first:
```bash
git add .
git commit -m "Save my changes"
git pull ik main
```
**3. Test After Updates**
Run your app locally to verify everything works:
```bash
pnpm dev
```
**4. Review Changes**
Check what was updated:
```bash
git log --oneline -10 # See last 10 commits
```
## Troubleshooting ๐ง
**"Please commit your changes before merging"**
* Save your work first: `git add . && git commit -m "Save changes"`
* Then pull updates: `git pull ik main`
**Too many conflicts?**
* Watch the video tutorial for conflict resolution tips
* Focus on one file at a time
* When in doubt, keep your customized code
**"Remote 'ik' not found"**
* You need to add the remote first (Step 1)
* Run: `git remote add ik `
**Updates broke something?**
* Revert to previous version: `git revert HEAD`
* Check what changed: `git diff HEAD~1`
* Ask for help in Discord!
## Next Steps ๐
1. **Set a reminder** - Update monthly or quarterly
2. **Join Discord** - Get notified of important updates
3. **Check changelog** - See what's new in each release
4. **Test thoroughly** - Verify everything works after updating
Regular updates keep your project secure, fast, and feature-rich! ๐ฏ
## Related Documentation ๐
* [IDE Setup](/codebase/ide-setup) - Set up your editor
* [Formatting & Linting](/codebase/formatting-and-linting) - Code quality tools
# Multi Tenant Kit (/multi-tenancy/b2b-kit)
# Multi Tenant Kit ๐ข
Build powerful multi-tenant B2B SaaS applications with organization management, role-based access control, and subscription billing! โจ
## What is Multi Tenant Kit? ๐ค
The Multi Tenant Kit transforms Indie Kit into a complete B2B SaaS platform with:
* ๐ข **Multi-Organization Support**: Users can belong to multiple organizations
* ๐ฅ **Role-Based Access Control**: Owner, Admin, and User roles with permissions
* ๐ณ **Subscription Management**: Integrate with Stripe or LemonSqueezy
* ๐ **Super Admin Dashboard**: Manage all organizations and plans
* ๐ **Plan Quotas**: Limit features based on subscription tiers
* โ๏ธ **Team Invitations**: Invite members to your organization
* ๐ **Secure API Routes**: Built-in authentication wrappers
Multi Tenant Kit is a separate boilerplate that extends Indie Kit with multi-tenancy features. Perfect for building tools like Slack, Notion, or Figma!
## Installation ๐
If you don't have the Multi Tenant Kit yet:
```bash
git clone https://github.com/Indie-Kit/b2b-boilerplate
cd b2b-boilerplate
pnpm install
```
You need a valid Indie Kit B2B license to access this repository. [Get your license here](https://indiekit.pro/app/pricing)!
## Quick Navigation ๐บ๏ธ
Jump to what you need:
* [Database Structure](#database-structure) - Understanding the data models
* [Organizations](#organizations) - How organizations work
* [Authentication](#authentication--security) - Securing your routes
* [Roles & Permissions](#roles--permissions) - Managing access control
* [Invitations](#invitations--memberships) - Team management
* [Plans & Billing](#plans--subscriptions) - Subscription management
* [Hooks & Utilities](#hooks--utilities) - Developer tools
## Database Structure ๐๏ธ
The Multi Tenant Kit uses a relational database structure designed for multi-tenancy:
### Key Relationships ๐
* **Users โ Organizations**: Many-to-many through `OrganizationMembership`
* **Organizations โ Plan**: Each organization has one plan (or null for free tier)
* **Organizations โ Invites**: Organizations can send multiple invitations
* **Organizations โ Billing**: Connected to Stripe/LemonSqueezy for payments
All tables use Drizzle ORM. Check `src/db/schema/` for the complete type-safe schema definitions!
## Organizations ๐ข
Organizations are the core of the Multi Tenant Kit! Users can create and join multiple organizations, each with its own team, subscription, and settings.
### How Organizations Work ๐
Think of organizations like workspaces in Slack or teams in Figma:
* ๐ค **Multiple Users**: An organization can have many team members
* ๐ฅ **Multiple Organizations Per User**: Users can belong to many organizations
* ๐ญ **Different Roles**: Each user has a specific role in each organization
* ๐ณ **Independent Billing**: Each organization has its own subscription
* โ๏ธ **Isolated Data**: Organization data is completely separated
### Organization Features โจ
**1. Unique Identity**
* ID, name, and slug for URL routing
* Optional organization image/logo
* Timestamps for tracking
**2. Role-Based Access Control**
* Owner: Full control (billing, deletion, everything)
* Admin: Manage members and settings
* User: Basic access to features
**3. Onboarding System**
* Track if onboarding is complete
* Store onboarding data (JSON)
* Custom onboarding flows
**4. Billing Integration**
* Stripe or LemonSqueezy support
* Customer and subscription IDs stored
* Automatic plan assignment
**5. Plan Management**
* Each organization has a plan (or null for free)
* Plan quotas control feature access
* Easy plan upgrades/downgrades
The current organization is stored in a **cookie session**! When you log in, your first organization is automatically selected. Use `switchOrganization()` to change the active organization without navigating away.
## Authentication & Security ๐
The Multi Tenant Kit includes powerful authentication wrappers to protect your API routes. Choose the right wrapper for your security needs!
### Three Levels of Protection ๐ก๏ธ
| Wrapper | Protection Level | Use Case |
| ------------------------------ | -------------------------- | -------------------------------------- |
| `withAuthRequired` | User must be logged in | Personal data, user settings |
| `withOrganizationAuthRequired` | User + Organization + Role | Organization resources, team features |
| `withSuperAdminAuthRequired` | Super admin only | Platform management, all organizations |
### 1. Basic Authentication: `withAuthRequired` ๐ค
Requires user to be logged in. Perfect for personal features:
```typescript
// src/app/api/app/profile/route.ts
import withAuthRequired from "@/lib/auth/withAuthRequired";
export const GET = withAuthRequired(async (req, context) => {
const user = await context.session.user;
// Fetch user's personal data
const profile = await getUserProfile(user.id);
return NextResponse.json(profile);
});
```
**Use this for:**
* User profile endpoints
* Personal settings
* User-specific data (not org-specific)
### 2. Organization Authentication: `withOrganizationAuthRequired` ๐ข
Requires user to belong to the organization + optional role check:
```typescript
// src/app/api/app/org/[slug]/settings/route.ts
import withOrganizationAuthRequired from "@/lib/auth/withOrganizationAuthRequired";
import { OrganizationRole } from "@/db/schema/organization";
export const PATCH = withOrganizationAuthRequired(
async (req, context) => {
const organization = await context.session.organization;
const body = await req.json();
// Update organization settings
await updateSettings(organization.id, body);
return NextResponse.json({ success: true });
},
OrganizationRole.enum.admin // Only admin or owner
);
```
**Use this for:**
* Organization resources (projects, documents, etc.)
* Team settings
* Member management
* Anything scoped to an organization
[Learn more about Organization Auth โ](/multi-tenancy/with-organization-auth-required)
### 3. Super Admin Authentication: `withSuperAdminAuthRequired` ๐
Requires super admin privileges. For platform management only:
```typescript
// src/app/api/super-admin/organizations/route.ts
import withSuperAdminAuthRequired from "@/lib/auth/withSuperAdminAuthRequired";
export const GET = withSuperAdminAuthRequired(async (req, context) => {
// Fetch all organizations (admin view)
const allOrgs = await getAllOrganizations();
return NextResponse.json(allOrgs);
});
```
**Use this for:**
* Managing all organizations
* Creating/editing plans
* Platform analytics
* User impersonation
Always use the most restrictive wrapper for your use case! If an endpoint needs organization context, use `withOrganizationAuthRequired`, not just `withAuthRequired`.
## Roles & Permissions ๐ฅ
The Multi Tenant Kit uses a simple but powerful three-tier role system:
```typescript
export const roleEnum = pgEnum("role", ["owner", "admin", "user"]);
```
### Role Hierarchy ๐
```
๐ Owner (Level 2)
โ Can do everything
๐ค Admin (Level 1)
โ Can do most things
๐ User (Level 0)
โ Can use features
```
### Role Permissions ๐
| Permission | Owner | Admin | User |
| --------------------- | ----- | ----- | ---- |
| Delete organization | โ | โ | โ |
| Manage billing | โ | โ | โ |
| Invite/remove members | โ | โ | โ |
| Change member roles | โ | โ | โ |
| Update org settings | โ | โ | โ |
| Use features | โ | โ | โ |
### Role Checking in Code ๐ป
The `hasHigherOrEqualRole` function compares roles:
```typescript
const roleHierarchy = {
user: 0,
admin: 1,
owner: 2,
};
// Check if user can perform admin actions
if (hasHigherOrEqualRole({
currentRole: userRole,
requiredRole: 'admin'
})) {
// Allow action
}
```
This means:
* **Owners** can do everything Admins and Users can do
* **Admins** can do everything Users can do
* **Users** have only their own permissions
[Learn more about managing roles โ](/multi-tenancy/roles)
## Invitations & Memberships โ๏ธ
Build your team by inviting members to your organization!
### How Memberships Work ๐ฅ
Organization memberships connect users to organizations:
```typescript
OrganizationMembership {
organizationId: string // Which organization
userId: string // Which user
role: 'owner' | 'admin' | 'user' // Their role
createdAt: timestamp
updatedAt: timestamp
}
```
**Key Features:**
* One user can belong to multiple organizations
* Each membership has a specific role
* Composite primary key prevents duplicates
* Timestamps track when they joined
### The Invitation System ๐จ
Admins and Owners can invite new members:
```typescript
OrganizationInvite {
id: string // Unique invite ID
email: string // Invitee's email
organizationId: string // Target organization
role: 'owner' | 'admin' | 'user' // Assigned role
token: string // Verification token
expiresAt: timestamp // When invite expires
createdAt: timestamp
}
```
**How it Works:**
1. Admin/Owner sends invite with email and role
2. System creates invite with unique token
3. Email sent to invitee with link
4. Invitee clicks link, creates account (if needed)
5. Membership created automatically
6. Invite marked as used or deleted
Invitations expire after a set period (default: 7 days). This prevents unused invites from piling up and improves security!
[Learn more about inviting members โ](/multi-tenancy/inviting-team-members)
## Plans & Subscriptions ๐ณ
Monetize your B2B SaaS with flexible subscription plans!
### Understanding Plans ๐
Plans define what organizations can do and how much they pay:
**Plan Structure:**
* Name and codename (for URLs)
* Default flag (free tier)
* Pricing tiers (monthly, yearly, one-time)
* Payment provider integration
* Feature quotas
**Supported Pricing Models:**
* ๐ฐ **Monthly**: Recurring monthly charge
* ๐ **Yearly**: Annual subscription (usually discounted)
* ๐ฏ **One-time**: Lifetime access or credits
**Payment Providers:**
* Stripe (recommended)
* LemonSqueezy
### Creating Plans (Super Admin) ๐
Plans are managed through the super admin dashboard:
1. Navigate to `/super-admin/plans`
2. Click **"Create Plan"**
3. Fill in the details:
```
Plan Details:
โโ Name: "Professional"
โโ Codename: "pro"
โโ Default: false
โโ Monthly Price: $49
โโ Yearly Price: $490 (save $98!)
โโ Stripe Price IDs
โโ Quotas: { projects: 100, users: 50 }
```
4. Save and it's live!
Quotas are stored as JSON and can be anything: `{ projects: 100, storage: "50GB", apiCalls: 10000 }`. Check quotas in your code to limit features!
### Generating Subscription Links ๐
Use `getSubscribeUrl` to create subscription links:
```tsx
import Link from "next/link";
import getSubscribeUrl from "@/lib/plans/getSubscribeUrl";
import { PlanType, PlanProvider } from "@/lib/plans/getSubscribeUrl";
export function PricingCard() {
return (
Pro Plan
$49/month
Start Free Trial
);
}
```
**getSubscribeUrl Parameters:**
* `codename`: Plan identifier (e.g., "pro", "enterprise")
* `type`: MONTHLY, YEARLY, or ONETIME
* `provider`: STRIPE or LEMON\_SQUEEZY
* `trialPeriodDays`: Optional trial period
### Checking Plan Quotas in Code ๐
Enforce plan limits in your application:
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function CreateProjectButton() {
const { organization } = useOrganization()
// Check if they can create more projects
const currentProjects = 45 // From your database
const maxProjects = organization.plan?.quotas.projects || 10
const canCreate = currentProjects < maxProjects
return (
)
}
```
## Hooks & Utilities ๐ช
The Multi Tenant Kit provides powerful React hooks and utility functions for your application!
### `useOrganization` Hook ๐ข
Your go-to hook for accessing organization data in client components:
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export default function Dashboard() {
const {
organization, // Current org data with plan and role
isLoading, // Loading state
error, // Error state
mutate, // Refresh data
switchOrganization // Switch to another org
} = useOrganization()
if (isLoading) return
Loading...
if (error) return
Error loading organization
return (
{organization.name}
Plan: {organization.plan?.name}
Your Role: {organization.role}
)
}
```
**What you get:**
* Organization name, slug, image
* Current user's role in the organization
* Plan details and quotas
* Loading and error states
* Functions to refresh or switch organizations
[Full useOrganization documentation โ](/multi-tenancy/use-organization)
### Organization Utility Functions ๐ ๏ธ
Server-side utilities for working with organizations:
#### `getUserOrganizations(userId)`
Fetches all organizations a user belongs to:
```typescript
const userOrgs = await getUserOrganizations(userId);
// Returns: Array<{id, name, slug, image, role, onboardingDone}>
```
#### `getUserOrganizationById(userId, organizationId)`
Fetches a specific organization with plan:
```typescript
const org = await getUserOrganizationById(userId, organizationId);
// Returns: {id, name, slug, role, plan: {id, name, quotas, ...}}
```
#### `createOrganization(name, creatorId)`
Creates a new organization:
```typescript
const newOrg = await createOrganization("Acme Corp", userId);
// Creator automatically becomes owner
```
#### `hasHigherOrEqualRole({currentRole, requiredRole})`
Checks if a role meets requirements:
```typescript
if (hasHigherOrEqualRole({
currentRole: 'admin',
requiredRole: 'admin'
})) {
// Allow action
}
```
#### `userBelongsToOrganization(userId, organizationId)`
Verifies membership:
```typescript
const isMember = await userBelongsToOrganization(userId, orgId);
// Returns: boolean
```
All utilities are fully typed with TypeScript! Your IDE will autocomplete everything.
## Quick Start Guide ๐
Ready to build? Here's how to get started:
### 1. Install Multi Tenant Kit โ
```bash
git clone https://github.com/Indie-Kit/b2b-boilerplate
cd b2b-boilerplate
pnpm install
```
### 2. Set Up Database ๐๏ธ
```bash
pnpm db:push # Push schema to database
```
### 3. Create Your First Plan ๐ณ
1. Run the app: `pnpm dev`
2. Make yourself super admin (check docs)
3. Go to `/super-admin/plans`
4. Create a "Free" plan with default: true
5. Create paid plans (Pro, Enterprise, etc.)
### 4. Build Organization Features ๐ข
Use the provided hooks and wrappers:
```tsx
// Client component with organization data
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
// API route with organization auth
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
```
### 5. Test Everything โจ
* Create an organization
* Invite team members
* Subscribe to a plan
* Test role permissions
* Verify quota enforcement
## Related Documentation ๐
Deep dive into specific topics:
* [Organization Roles](/multi-tenancy/roles) - Managing permissions
* [useOrganization Hook](/multi-tenancy/use-organization) - Client-side hook
* [withOrganizationAuthRequired](/multi-tenancy/with-organization-auth-required) - API security
* [Inviting Team Members](/multi-tenancy/inviting-team-members) - Team management
Remember: The current organization is managed via **cookie session**, not URL routing! This means users stay on the same URL when switching organizations - the context changes seamlessly in the background.
You now have everything you need to build a powerful B2B SaaS application! Start with the Quick Start Guide above and refer back to specific sections as needed. ๐
# Inviting Team Members (/multi-tenancy/inviting-team-members)
# Inviting Team Members ๐ฅ
Invite members to your organization! The complete team management system is built-in and ready to use. โจ
Only **Admins** and **Owners** can invite members. Regular users can view the team but cannot send invitations.
## Using Team Management (Built-In) ๐
The team management UI is already implemented! Just use it:
### Navigate to Team Page
Go to **Settings** โ **Team** in your organization.
### Invite New Members
1. Click **"Invite Member"** button
2. Enter their email address
3. Select role: **User** or **Admin**
4. Click **"Send Invitation"**
Done! The invitation email is sent automatically. ๐ง
The team page includes everything: member list, pending invites, role management, and removal. No code needed!
### Manage Your Team
From the Team page:
* ๐ฅ **View members** - See all team members with avatars and roles
* ๐ง **Pending invites** - Track sent invitations and expiry dates
* ๐ **Change roles** - Click menu โ "Change Role"
* โ **Remove members** - Click menu โ "Remove Member"
* ๐ซ **Cancel invites** - Revoke pending invitations
## How Invitations Work ๐ฏ
**Complete flow:**
1. **Admin sends invite** โ Email + role specified
2. **Email sent** โ Unique link with 7-day expiry
3. **User accepts** โ Clicks link, creates account if needed
4. **Membership created** โ User joins with assigned role
5. **Invite deleted** โ Cleanup after acceptance
**Security features:**
* โ Invitations expire after 7 days
* โ One-time use tokens
* โ Email verification required
* โ Role-based access control
Email sending, token generation, expiry handling, and membership creation are all automatic! Just use the UI.
## Customization (Advanced) ๐จ
Want to customize the invitation system? You can modify these files:
### Email Templates
**Invitation email:**
```
src/emails/InvitationEmail.tsx
```
Customize the design, content, and branding of invitation emails.
**Access revoked email:**
```
src/emails/AccessRevokedEmail.tsx
```
Customize the email sent when a member is removed.
### Team Page UI
**Complete team management page:**
```
src/app/(in-app)/(organization)/app/settings/team/page.tsx
```
The page includes:
* `` - Main team page layout
* `` - Member list with table
* `` - Pending invitations list
* `` - Invite member modal
* `` - Cancel invite modal
* `` - Change role modal
* `` - Remove member modal
**What you can customize:**
* Table layout and styling
* Dialog designs
* Add custom fields (department, title, etc.)
* Bulk invite actions
* Custom validation rules
All team management code is in your project! Customize the UI, emails, and logic to match your needs.
## Example Customizations ๐ก
### Custom Invitation Email
Edit `src/emails/InvitationEmail.tsx`:
```tsx
export default function InvitationEmail({
organizationName,
inviterName,
role,
inviteUrl,
}) {
return (
๐ Join {organizationName}!
{inviterName} has invited you to join{' '}
{organizationName} as a {role}.
)
}
```
### Add Department Field
Extend the invitation to include department:
1. **Update invite dialog** - Add department input field
2. **Update API** - Include department in request
3. **Store in database** - Add department to memberships table
4. **Display in UI** - Show department in member list
## Troubleshooting ๐ง
**Can't see Invite button?**
* Check your role (must be Admin or Owner)
* Verify you're on the Team page
* Look in Settings โ Team
**Invitation not sending?**
* Check [email provider](/setup/email) is configured
* Verify email template exists
* Test with your own email first
* Check server logs for errors
**Member not receiving email?**
* Check spam/junk folder
* Verify email address is correct
* Resend the invitation
* Check email provider logs
**Can't remove member?**
* Must be Admin or Owner
* Can't remove the organization owner
* Check for role permissions
Always test invitations with your own email before inviting real team members!
## Next Steps ๐
1. **Test the system** - Invite yourself to see the flow
2. **Customize emails** - Update templates to match your brand
3. **Customize UI** - Modify team page if needed
4. **Understand roles** - Learn about [role permissions](/multi-tenancy/roles)
Your team management system is production-ready! Start inviting members and building your team. ๐ฅ
## Related Documentation ๐
* [Organization Roles](/multi-tenancy/roles) - Understanding role permissions
* [useOrganization Hook](/multi-tenancy/use-organization) - Client-side org data
* [withOrganizationAuthRequired](/multi-tenancy/with-organization-auth-required) - API security
* [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit) - Complete B2B guide
# Roles (/multi-tenancy/roles)
# Organization Roles ๐ฅ
The Multi Tenant Kit includes a powerful role-based permission system to control what members can do in your organization! โจ
## Understanding Roles ๐ฏ
Roles determine what actions members can perform within an organization. Each member has exactly one role that defines their level of access.
Multi Tenant Kit includes three pre-configured roles: **Owner**, **Admin**, and **User**. These cover most B2B SaaS use cases!
Need more roles? You can customize the role system by updating the `roleEnum` in `src/db/schema/organization.ts`. Add roles like **Manager**, **Viewer**, or **Contributor** to match your use case!
## The Three Roles ๐
### Owner (Highest Permission)
The organization creator and ultimate authority:
* โ **Full Access**: Can do everything in the organization
* โ **Billing Management**: Control subscriptions and payments
* โ **Delete Organization**: Can permanently delete the organization
* โ **Transfer Ownership**: Can assign ownership to another member
* โ **All Admin Permissions**: Everything an admin can do, plus more
**Use Case**: Typically the person who created the organization or pays for the subscription.
### Admin (Management Permission)
Trusted team members who help manage the organization:
* โ **Member Management**: Invite, remove, and change member roles
* โ **Settings Management**: Update organization settings and preferences
* โ **Resource Management**: Create, update, and delete organization resources
* โ **View Billing**: Can see billing information (but not modify)
* โ **Cannot Delete Organization**: Only owners can delete
* โ **Cannot Transfer Ownership**: Only owners can change ownership
**Use Case**: Team leads, managers, or trusted employees who help run the organization.
### User (Basic Access)
Standard members with basic permissions:
* โ **Use Features**: Access and use the application's features
* โ **View Organization Info**: See organization details
* โ **Manage Own Profile**: Update their own profile
* โ **Cannot Invite Members**: Cannot invite new people
* โ **Cannot Change Settings**: Cannot modify organization settings
* โ **Cannot Access Billing**: Cannot see billing information
**Use Case**: Regular team members, employees, or collaborators.
## Role Hierarchy ๐
Roles have a hierarchical structure where higher roles have all permissions of lower roles:
```typescript
const roleHierarchy = {
user: 0, // Lowest
admin: 1, // Middle
owner: 2, // Highest
}
```
This means:
* **Owners** can do everything Admins and Users can do
* **Admins** can do everything Users can do
* **Users** have only their own permissions
## Protecting API Routes with Roles ๐
Use `withOrganizationAuthRequired` to protect routes and enforce role requirements:
### Require Minimum Role
```tsx
// src/app/api/app/org/[slug]/settings/route.ts
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
import { OrganizationRole } from '@/db/schema/organization'
import { NextResponse } from 'next/server'
// Only admins and owners can access this route
export const PATCH = withOrganizationAuthRequired(
async (req, context) => {
const { organization } = context.session
const body = await req.json()
// Update organization settings
await updateOrganizationSettings(organization.id, body)
return NextResponse.json({ success: true })
},
OrganizationRole.enum.admin // Requires admin role or higher
)
```
### Owner-Only Routes
```tsx
// src/app/api/app/org/[slug]/delete/route.ts
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
import { OrganizationRole } from '@/db/schema/organization'
// Only owners can delete organizations
export const DELETE = withOrganizationAuthRequired(
async (req, context) => {
const { organization } = context.session
// Delete organization
await deleteOrganization(organization.id)
return NextResponse.json({ success: true })
},
OrganizationRole.enum.owner // Requires owner role
)
```
### User-Level Routes (No Minimum Role)
```tsx
// src/app/api/app/org/[slug]/projects/route.ts
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
// Any organization member can access
export const GET = withOrganizationAuthRequired(
async (req, context) => {
const { organization } = context.session
// Fetch projects
const projects = await getProjects(organization.id)
return NextResponse.json(projects)
}
// No role parameter = any member can access
)
```
When you specify a minimum role, the wrapper automatically allows users with that role **or higher**. For example, `admin` allows both admins and owners!
## Checking Roles in Components ๐จ
### Using useOrganization Hook
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export default function OrganizationSettings() {
const { organization } = useOrganization()
// Get current user's role
const userRole = organization.membership?.role
const isOwner = userRole === 'owner'
const isAdmin = userRole === 'admin' || userRole === 'owner'
return (
Organization Settings
{/* Show delete button only to owners */}
{isOwner && (
)}
{/* Show invite button to admins and owners */}
{isAdmin && (
)}
{/* Everyone sees this */}
Organization Name: {organization.name}
)
}
```
### Checking Role Hierarchy
Use the `hasHigherOrEqualRole` utility:
```tsx
import { hasHigherOrEqualRole } from '@/lib/organizations/roles'
// Check if user has sufficient role
const canManageMembers = hasHigherOrEqualRole(
currentUserRole,
'admin'
)
if (canManageMembers) {
// Show member management UI
}
```
## Assigning Roles to Members ๐ค
### When Inviting New Members
Specify the role when creating an invitation:
```tsx
// src/app/api/app/org/[slug]/invites/route.ts
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
import { OrganizationRole } from '@/db/schema/organization'
export const POST = withOrganizationAuthRequired(
async (req, context) => {
const { organization } = context.session
const { email, role } = await req.json()
// Create invitation with specified role
await createInvitation({
organizationId: organization.id,
email,
role: role || 'user', // Default to 'user' if not specified
})
return NextResponse.json({ success: true })
},
OrganizationRole.enum.admin // Only admins can invite
)
```
### Changing Member Roles
Update an existing member's role:
```tsx
// src/app/api/app/org/[slug]/members/[userId]/route.ts
import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired'
import { OrganizationRole } from '@/db/schema/organization'
export const PATCH = withOrganizationAuthRequired(
async (req, context) => {
const { organization } = context.session
const { userId } = context.params
const { role } = await req.json()
// Update member role
await updateMemberRole(organization.id, userId, role)
return NextResponse.json({ success: true })
},
OrganizationRole.enum.admin // Only admins can change roles
)
```
## Role-Based UI Examples ๐ฏ
### Conditional Rendering
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function MembersList() {
const { organization } = useOrganization()
const isAdmin = ['admin', 'owner'].includes(organization.membership?.role)
return (
Team Members
{/* List all members */}
{organization.members.map(member => (
{member.name}
{member.role}
{/* Only admins see action buttons */}
{isAdmin && (
)}
))}
{/* Only admins see invite button */}
{isAdmin && (
)}
)
}
```
### Navigation Based on Roles
```tsx
'use client'
import Link from 'next/link'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function OrgNavigation() {
const { organization } = useOrganization()
const role = organization.membership?.role
const isOwner = role === 'owner'
const isAdmin = ['admin', 'owner'].includes(role)
return (
)
}
```
## Best Practices ๐ก
### 1. Always Protect API Routes
```tsx
// โ Bad - No role check
export async function DELETE(req: Request) {
await deleteOrganization()
}
// โ Good - Protected with role requirement
export const DELETE = withOrganizationAuthRequired(
async (req, context) => {
await deleteOrganization()
},
OrganizationRole.enum.owner
)
```
### 2. Use Role Hierarchy
```tsx
// โ Bad - Checking each role individually
if (role === 'admin' || role === 'owner') {
// Allow action
}
// โ Good - Using hasHigherOrEqualRole
if (hasHigherOrEqualRole(role, 'admin')) {
// Allow action
}
```
### 3. Default to Least Privilege
```tsx
// โ Default to 'user' role when inviting
const role = invitationData.role || 'user'
```
### 4. Hide UI for Unauthorized Actions
```tsx
// โ Don't show buttons users can't use
{isAdmin && (
)}
```
### 5. Validate on Backend
```tsx
// โ Always validate permissions on the server
export const DELETE = withOrganizationAuthRequired(
async (req, context) => {
// Server-side validation
if (context.session.membership.role !== 'owner') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 403 }
)
}
// Proceed with deletion
}
)
```
## Common Use Cases ๐
### Creating a Members Page
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
import { useState } from 'react'
export default function MembersPage() {
const { organization } = useOrganization()
const [inviteEmail, setInviteEmail] = useState('')
const [inviteRole, setInviteRole] = useState('user')
const isAdmin = ['admin', 'owner'].includes(
organization.membership?.role
)
const handleInvite = async () => {
await fetch(`/api/app/org/${organization.slug}/invites`, {
method: 'POST',
body: JSON.stringify({ email: inviteEmail, role: inviteRole }),
})
}
return (
Team Members
{/* Invite form - only for admins */}
{isAdmin && (
)
}
```
## Troubleshooting ๐ง
### "Unauthorized" Error
Make sure you're using the correct role wrapper:
```tsx
// Check that you have the required role
export const PATCH = withOrganizationAuthRequired(
async (req, context) => {
// Your code
},
OrganizationRole.enum.admin // Make sure you have admin role
)
```
### Role Not Updating
After changing roles, refresh the session:
```tsx
import { mutate } from 'swr'
// After updating role
await updateMemberRole(userId, newRole)
mutate('/api/app/org/current') // Refresh organization data
```
### Can't Access Organization Data
Make sure you're inside an organization context:
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export default function MyComponent() {
const { organization, isLoading } = useOrganization()
if (isLoading) return
Loading...
if (!organization) return
No organization selected
// Now you can safely access organization data
}
```
## Summary ๐
You now know how to:
* โ Understand the three role types (Owner, Admin, User)
* โ Protect API routes with role requirements
* โ Check roles in components
* โ Assign and change member roles
* โ Build role-based UIs
* โ Follow security best practices
With roles properly configured, you can build secure multi-user organizations! Remember to always validate permissions on the backend. ๐
## Related Documentation ๐
* [Inviting Team Members](/multi-tenancy/inviting-team-members)
* [Using useOrganization Hook](/multi-tenancy/use-organization)
* [Organization Auth Required](/multi-tenancy/with-organization-auth-required)
* [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit)
# useOrganization (/multi-tenancy/use-organization)
# useOrganization Hook ๐ข
The `useOrganization` hook is your primary way to access and manage organization data in client components! โจ
## What It Does ๐ฏ
The hook provides:
* ๐ข Current organization data (name, slug, plan, etc.)
* ๐ฅ User's membership info (role, permissions)
* ๐ Loading and error states
* ๐ Ability to refresh data
* ๐ Function to switch organizations
This hook uses SWR and must be used in client components. Add `'use client'` at the top of your file!
## Basic Usage ๐
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export default function Dashboard() {
const { organization, isLoading, error } = useOrganization()
if (isLoading) return
Loading...
if (error) return
Error loading organization
return (
Welcome to {organization.name}
Your role: {organization.role}
)
}
```
## Return Values ๐ฆ
The hook returns an object with the following properties:
```typescript
{
organization: {
id: string
name: string
slug: string
image?: string
onboardingDone: boolean
role: 'owner' | 'admin' | 'user'
plan: {
id: string
name: string
codename: string
default: boolean
quotas: object
requiredCouponCount: number
} | null
}
isLoading: boolean
error: Error | null
mutate: () => void
switchOrganization: (organizationId: string) => Promise
}
```
The `plan` property can be `null` if the organization doesn't have a plan assigned yet. Always check for null before accessing plan properties!
## Common Use Cases ๐
### Display Organization Info
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function OrganizationHeader() {
const { organization, isLoading } = useOrganization()
if (isLoading) return
Loading...
return (
{organization.image && (
)}
{organization.name}
{organization.plan?.name || 'No Plan'} Plan
)
}
```
### Check User Role
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function MembersButton() {
const { organization } = useOrganization()
const isAdmin = ['admin', 'owner'].includes(organization.role)
if (!isAdmin) return null
return (
)
}
```
### Access Plan Quotas
```tsx
'use client'
import { useOrganization } from '@/lib/organizations/useOrganization'
export function ProjectsPage() {
const { organization } = useOrganization()
if (!organization.plan) {
return
No plan assigned
}
const { projects } = organization.plan.quotas
const currentProjects = 5 // Fetch from your data
const canCreateMore = currentProjects < projects
return (
```
### 3. Limited Time Urgency
```tsx
Get lifetime access at this price before it increases! Only available during our launch week.
```
## Best Practices โ
**1. Clear Pricing Display**
```tsx
// โ Good - Clear and upfront
$499 - Lifetime Access
One-time payment, no recurring fees
// โ Bad - Confusing
$499
```
**2. Highlight Value**
* Show what they get forever
* Compare to subscription cost
* Emphasize "no recurring fees"
* Display savings percentage
**3. Feature Comparison**
```tsx
Monthly: $29/mo
All features
= $348/year
Lifetime: $299
Same features
Pay once, use forever! ๐
```
**4. Test Everything**
* โ Test payment flow end-to-end
* โ Verify instant access after payment
* โ Check webhook handling
* โ Test with test cards/sandbox mode
* โ Confirm plan assignment
## Troubleshooting ๐ง
**Payment not processing?**
* Check price ID matches payment provider
* Verify `hasOnetimePricing` is true
* Ensure webhooks are configured
* Test in provider's test mode first
**User not getting access?**
* Check webhook received successfully
* Verify plan assignment logic
* Check database for subscription record
* Look at webhook logs in provider dashboard
One-time payments rely on webhooks to grant access! Make sure your webhook endpoint is configured in your payment provider dashboard.
## Next Steps ๐
1. **Set up provider** - Configure Stripe/LemonSqueezy webhooks
2. **Create plan** - Enable one-time pricing in super admin
3. **Build pricing page** - Display lifetime option
4. **Test thoroughly** - Use test mode before going live
5. **Launch** - Start selling lifetime access!
Your one-time payment system is ready! Perfect for LTD campaigns, courses, and lifetime deals. ๐ฐ
## Related Documentation ๐
* [Managing Plans](/payments/managing-plans) - Create and edit plans
* [Running LTD Campaigns](/payments/running-ltd-campaigns) - Coupon-based LTDs
* [Stripe Setup](/setup/payments/stripe) - Configure Stripe
* [Subscription Payments](/payments/create-subscription) - Recurring billing
# Subscriptions (/payments/create-subscription)
YouTube Video: https://www.youtube.com/watch?v=twzpdNvl9DI
Start earning recurring revenue with subscriptions! Support monthly, yearly, and trial periods out of the box. ๐ฐ
Set up [Stripe](/setup/payments/stripe) or [LemonSqueezy](/setup/payments/lemonsqueezy), then create subscription plans in super admin.
## Setup Your Plans ๐ฏ
### Step 1: Create Subscription Plans
Navigate to `/super-admin/plans` and configure:
1. **Enable subscription types** - Monthly, yearly, or both
2. **Set prices** - Amount in cents (2900 = $29/month)
3. **Add price IDs** - From your payment provider
4. **Configure features** - What subscribers get
5. **Set quotas** - Usage limits per plan
Enable both monthly and yearly pricing! Offer 15-20% discount on yearly plans to incentivize annual subscriptions.
### Step 2: Generate Subscription Links
Use the `getSubscribeUrl` helper:
```tsx
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
// Monthly subscription
const monthlyUrl = getSubscribeUrl({
codename: "pro",
type: PlanType.MONTHLY,
provider: PlanProvider.STRIPE,
})
// Yearly subscription with discount
const yearlyUrl = getSubscribeUrl({
codename: "pro",
type: PlanType.YEARLY,
provider: PlanProvider.STRIPE,
})
// With free trial
const trialUrl = getSubscribeUrl({
codename: "pro",
type: PlanType.MONTHLY,
provider: PlanProvider.STRIPE,
trialPeriodDays: 14, // 14-day free trial
})
```
**Parameters:**
* `codename` - Plan identifier from your database
* `type` - `PlanType.MONTHLY` or `PlanType.YEARLY`
* `provider` - Stripe, LemonSqueezy, PayPal, DodoPayments, or Polar
* `trialPeriodDays` - Optional free trial (e.g., 7, 14, 30 days)
Adding a 7-14 day trial can increase conversions by 20-30%! Let users experience value before paying.
## Complete Pricing Page Example ๐
Build a beautiful pricing page with toggles:
```tsx
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Check } from 'lucide-react'
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
function PricingPage({ plans }) {
const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'yearly'>('monthly')
return (
)}
```
## Subscription Lifecycle ๐
### What Happens After Subscription
1. **Payment processed** - User charged immediately (or after trial)
2. **Webhook received** - Your app gets notified
3. **Plan assigned** - User gets access to features
4. **Recurring billing** - Auto-charged monthly/yearly
5. **Access managed** - Features gated by subscription status
### Handling Different States
```tsx
'use client'
import { useCurrentPlan } from '@/lib/subscription/useCurrentPlan'
function FeatureGate({ children }) {
const { currentPlan, isLoading } = useCurrentPlan()
if (isLoading) return
Loading...
if (!currentPlan) {
return (
Subscribe to Access
)
}
return <>{children}>
}
```
## Best Practices โ
**1. Clear Pricing Display**
```tsx
// โ Good - Clear and transparent
// โ Bad - Unclear terms
```
**3. Show What They Get**
* List all features clearly
* Highlight popular plan
* Show quota limits
* Compare plans side-by-side
**4. Easy Cancellation**
* Make cancellation accessible
* No dark patterns
* Clear in billing settings
* One-click cancellation
## Testing Your Subscriptions ๐งช
**1. Test Mode Setup**
```bash
# Use test keys
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_PUBLISHABLE_KEY="pk_test_..."
```
**2. Test Scenarios**
* โ New subscription
* โ Free trial expiration
* โ Successful renewal
* โ Failed payment
* โ Upgrade/downgrade
* โ Cancellation
* โ Reactivation
**3. Use Test Cards**
```
Successful: 4242 4242 4242 4242
Declined: 4000 0000 0000 0002
```
Always test the complete subscription flow in test mode before going live! Verify webhooks work correctly.
## Troubleshooting ๐ง
**Subscription not activating?**
* Check webhook is configured
* Verify price IDs match
* Look at webhook logs
* Test in provider dashboard
**User not seeing features?**
* Check plan assignment logic
* Verify subscription status
* Check database records
* Review webhook processing
**Renewals failing?**
* Customer payment method expired
* Card declined
* Webhook not processed
* Check provider dashboard
## Next Steps ๐
1. **Configure plans** in `/super-admin/plans`
2. **Set up webhooks** in payment provider
3. **Build pricing page** with toggle
4. **Add feature gates** based on plans
5. **Test thoroughly** before launch
Your subscription system is ready to generate recurring revenue! Start with a free trial to boost conversions. ๐ฐ
## Related Documentation ๐
* [Managing Plans](/payments/managing-plans) - Create and edit plans
* [Plan-Based Rendering](/payments/current-plan-based-rendering) - Gate features
* [Stripe Setup](/setup/payments/stripe) - Configure Stripe
* [One-Time Payments](/payments/create-one-time-payment) - Lifetime deals
# Credits System (/payments/credits-system)
# Credits System ๐ฐ
Charge users based on actual usage! Perfect for AI applications where you want to bill per image generated, video created, or API call made. โจ
Credits provide a flexible pay-as-you-go model that works alongside your subscription plans. Users buy credits and consume them as they use your features!
## Why Use Credits? ๐ค
**Perfect for:**
* ๐จ **AI Image Generation** - Charge per image created
* ๐ฅ **Video Processing** - Bill based on video length or quality
* ๐ค **API Calls** - Metered usage for AI services
* ๐ **Data Processing** - Usage-based pricing for compute-heavy tasks
* โจ **Freemium Models** - Give free credits, upsell more
**Benefits:**
* Fair pricing (users pay for what they use)
* Revenue from non-subscription users
* Incentivize trials with free credits
* Flexible monetization options
* Volume discounts made easy
## Enabling Credits
To enable the credits system in your application:
1. Open `src/lib/credits/config.ts`
2. Set `enableCredits` to `true`
```ts
export const enableCredits = true; // Enable or disable credits
```
3. The system will now be active and ready to configure! โจ
That's it! The entire credits system is now enabled. Next, configure your credit types and pricing below.
## Adding New Credit Types
Credit types define what users can purchase and use (e.g., "image\_generation", "video\_generation").
**Where to configure:** `src/lib/credits/config.ts`
**Example:**
```ts
import { z } from "zod";
// 1. Define your credit types in the schema
export const creditTypeSchema = z.enum([
"image_generation",
"video_generation",
// Add more types here
]);
// 2. Configure pricing for each type
export const creditsConfig: CreditsConfig = {
image_generation: {
name: "Image Generation Credits",
currency: "USD",
minimumAmount: 1,
slabs: [
{ from: 0, to: 1000, pricePerUnit: 0.01 },
{ from: 1001, to: 5000, pricePerUnit: 0.008 }, // Volume discount
],
},
video_generation: {
name: "Video Generation Credits",
currency: "USD",
minimumAmount: 1,
priceCalculator: (amount, userPlan) => {
// Custom pricing based on user's plan
return amount * 0.05;
},
},
};
```
**Pricing Models:**
* **Slabs** - Simple tiered pricing with volume discounts (recommended for most cases)
* **priceCalculator** - Advanced dynamic pricing based on user's subscription plan
## Auto Adding Credits on Signup
Reward new users with free credits when they register! ๐
**Where to configure:** `onRegisterCredits` in `src/lib/credits/config.ts`
**What to specify:**
* Credit type to grant
* Amount of credits to give
* Expiry duration (optional - credits won't expire if not set)
Free credits are perfect for letting users try your AI features before purchasing! Hook them with value, then convert to paid. ๐
## Adding Credits on Plan Change
Automatically grant credits when users upgrade or change their subscription plan.
**Where to configure:** `onPlanChangeCredits` in `src/lib/credits/config.ts`
**How it works:**
* Map plan codenames to credit grants
* Each plan can give different amounts of different credit types
* Set expiry periods per credit type
* Credits are added automatically when the plan change is detected
Include bonus credits with paid plans to incentivize upgrades! "Pro plan includes 1000 free credits per month" ๐
## Expiring Credits
Credits can automatically expire after a set duration (e.g., 30 days from grant).
**Configuration:**
Set `expiryAfter` (in days) when defining credits in:
* `onRegisterCredits` (signup bonuses)
* `onPlanChangeCredits` (plan upgrade bonuses)
**Cron Job Setup (if not using Inngest):**
1. Add to your `.env`:
```bash
CRON_USERNAME="your-secure-username"
CRON_PASSWORD="your-secure-password"
```
2. Setup on [cron-job.org](https://cron-job.org/en/):
* **URL**: `https://yourdomain.com/api/cron/expire-credits`
* **Auth**: HTTP Basic Authentication with your credentials
* **Schedule**: Daily at midnight (recommended)
The cron job runs daily and automatically expires credits based on your configuration. Set it once and forget it! โฐ
## Usage in Frontend
Two powerful hooks make it easy to work with credits:
### `useCredits` Hook
Fetch and display user's current credit balances.
**Returns:**
* `credits` - Object with all credit types and amounts
* `isLoading` - Loading state
* `error` - Any errors
* `mutate` - Function to refresh credits
**Example:**
```tsx
import useCredits from "@/lib/credits/useCredits";
function CreditBalance() {
const { credits, isLoading } = useCredits();
if (isLoading) return
Loading...
;
return (
Image Credits: {credits?.image_generation || 0}
Video Credits: {credits?.video_generation || 0}
);
}
```
### `useBuyCredits` Hook
Handle credit purchasing with automatic price calculation.
**Parameters:**
* Credit type
* Amount to purchase
**Returns:**
* `price` - Calculated price (considers user's plan for personalized pricing)
* `currency` - Currency for transaction
* `isLoading` - Loading state
* `error` - Any errors
* `getBuyCreditsUrl(provider)` - Generate payment URL
**Example:**
```tsx
import useBuyCredits from "@/lib/credits/useBuyCredits";
import { PlanProvider } from "@/lib/plans/getSubscribeUrl";
import { Button } from "@/components/ui/button";
function BuyCreditButton() {
const { price, isLoading, getBuyCreditsUrl } = useBuyCredits(
"image_generation",
100 // Buy 100 credits
);
const handlePurchase = () => {
const url = getBuyCreditsUrl(PlanProvider.STRIPE);
window.location.href = url;
};
return (
);
}
```
The `useBuyCredits` hook automatically calculates personalized pricing based on the user's current plan. Pro users might get better rates than free users!
**Benefits:**
* โก Automatic price calculation based on user's plan
* ๐ณ Support for multiple payment providers (Stripe, PayPal, DodoPayments, Polar)
* ๐ Real-time pricing updates
* ๐ฏ Seamless checkout experience
## Backend Functions
On the server side, you have access to powerful functions for managing credits programmatically.
### `deductCredits` Function
Deducts credits from a user's account with automatic balance checking.
**Parameters:**
* `userId` - User ID
* `creditType` - Type of credit to deduct
* `amount` - Amount to deduct
* `metadata` - Optional metadata for tracking
**Throws:** Error if user has insufficient credits
**Example:**
```ts
import { deductCredits } from "@/lib/credits/credits";
// In your API route or server action
async function generateImage(userId: string) {
try {
// Deduct credits before processing
await deductCredits(userId, "image_generation", 1, {
action: "generate_image",
prompt: "A beautiful sunset",
});
// Process the image generation
const image = await aiService.generateImage();
return { success: true, image };
} catch (error) {
// Handle insufficient credits
return { success: false, error: "Insufficient credits" };
}
}
```
### `addCredits` Function
Adds credits to a user's account with duplicate prevention.
**Parameters:**
* `userId` - User ID
* `creditType` - Type of credit to add
* `amount` - Amount to add
* `paymentId` - Payment ID (prevents duplicate credits)
* `metadata` - Optional metadata
* `expirationDate` - Optional expiration date
**Example:**
```ts
import { addCredits } from "@/lib/credits/credits";
// Grant bonus credits to a user
async function grantBonusCredits(userId: string) {
const result = await addCredits(
userId,
"image_generation",
50,
`bonus-${userId}-${Date.now()}`, // Unique payment ID
{ reason: "promotional_bonus" },
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // Expires in 30 days
);
return result;
}
```
Both functions include built-in safety checks: `deductCredits` prevents negative balances, and `addCredits` prevents duplicate credits using payment IDs!
**Use these functions for:**
* ๐จ Deducting credits when users consume AI services
* ๐ Granting bonus or promotional credits
* ๐ Refunding credits after failed operations
* ๐ Implementing custom credit workflows
## Payment Providers
Credits work with Stripe, PayPal, DodoPayments, Paddle, and Polar. LemonSqueezy doesn't support custom credit purchases yet.
**Supported payment providers:**
* โ **Stripe** - Full support with checkout
* โ **PayPal** - Full support
* โ **DodoPayments** - Full support with pay-as-you-go
* โ **Paddle** - Full support with automatic price creation
* โ **Polar** - Full support with pay-as-you-go pricing
### DodoPayments Setup
For DodoPayments credit purchases:
1. Create a product in DodoPayments dashboard
2. Set pricing model to **"Pay as you go"**
3. Add to `.env`:
```bash
DODO_CREDITS_PRODUCT_ID="pdt_xxxxxxxxxxxxx"
```
DodoPayments offers great rates and supports pay-as-you-go pricing natively. Perfect for credit systems! ๐ณ
### Paddle Setup
For Paddle credit purchases:
1. Go to Dashboard โ **Catalogue** โ **Products**
2. Create a product named **"Credits"**
3. **No need to create prices** - Indie Kit automatically creates them
4. Copy the Product ID and add to `.env`:
```bash
PADDLE_CREDITS_PRODUCT_ID="pro_xxxxxxxxxxxxx"
```
Paddle integrates seamlessly with the credits system. Indie Kit automatically handles price creation, so you only need to create the product itself! See [Paddle Setup](/setup/payments/paddle) for complete integration details. ๐ณ
### Polar Setup
For Polar credit purchases:
1. Create a product in Polar dashboard
2. Set pricing model to **"Pay what you want"**
3. Add to `.env`:
```bash
POLAR_CREDITS_PRODUCT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```
Polar.sh supports pay-what-you-want pricing natively. Perfect for dynamic credit purchases! ๐ป
## Super Admin
Administrators have full control over user credits from the admin panel.
**Features:**
* โ Add credits to any user
* โ Remove/deduct credits from users
* ๐ View credit transaction history
* ๐ Monitor credit usage patterns
**Access:** Navigate to user detail page in the super admin section to manage credits.
Super admins can manually adjust credits for customer support, refunds, or promotions. Great for handling edge cases! ๐
**Common use cases:**
* ๐ฌ Customer support (issuing refunds or bonuses)
* ๐ง Fixing billing issues
* ๐ Running promotions and giveaways
* ๐งช Testing features with test users
## Quick Recap ๐ฏ
With the credits system enabled, you can:
โ **Create multiple credit types** for different features\
โ **Auto-grant credits** on signup and plan changes\
โ **Set credit expiry periods** to create urgency\
โ **Offer personalized pricing** based on subscription tiers\
โ **Use frontend hooks** (`useCredits`, `useBuyCredits`) for UI\
โ **Use backend functions** (`deductCredits`, `addCredits`) for logic\
โ **Manage credits via admin panel** for support\
โ **Accept payments** through Stripe, PayPal, DodoPayments, Paddle, or Polar
You now have a complete usage-based pricing system! Perfect for AI SaaS, API platforms, or any service where you want to charge based on actual usage. ๐
## Related Documentation ๐
* [Stripe Setup](/setup/payments/stripe) - Configure Stripe payments
* [Paddle Setup](/setup/payments/paddle) - Configure Paddle payments
* [Create One-Time Payment](/payments/create-one-time-payment) - One-off purchases
* [Subscription Plans](/payments/create-subscription) - Recurring billing
* [Managing Plans](/payments/managing-plans) - Create and edit payment plans
* [Super Admin](/setup/super-admin) - Platform administration
# Plan-Based Rendering (/payments/current-plan-based-rendering)
YouTube Video: https://www.youtube.com/watch?v=JdlB2nwoUuM
Show different features to different plans! Gate premium features, enforce quotas, and create upgrade prompts based on user subscriptions. ๐ฏ
Use the `useCurrentPlan` hook to check user plans and quotas in client components, or `withAuthRequired` for API routes!
## Client-Side: useCurrentPlan Hook ๐จ
Access user plan data in any client component:
```tsx
'use client'
import { useCurrentPlan } from '@/lib/subscription/useCurrentPlan'
import { Button } from '@/components/ui/button'
import { CreditCard, Crown } from 'lucide-react'
import Link from 'next/link'
function Dashboard() {
const { currentPlan, isLoading } = useCurrentPlan()
if (isLoading) return
)
}
```
## Best Practices โ
**1. Graceful Degradation**
```tsx
// โ Good - Clear message with upgrade path
if (!hasAccess) {
return (
This feature requires Pro plan
)
}
// โ Bad - Just hiding with no explanation
if (!hasAccess) return null
```
**2. Show Limits Proactively**
```tsx
// โ Good - Show before they hit limit
Projects: {current}/{limit}
{current >= limit * 0.8 && (
Almost at limit! Consider upgrading.
)}
// โ Bad - Only show after hitting limit
```
**3. Consistent UI**
* Keep layout same across plans
* Show locked features (with lock icon)
* Make upgrade CTAs clear
* Don't frustrate free users
**4. Performance**
* Use SWR/React Query for plan data
* Cache plan checks
* Avoid re-fetching on every render
* Show loading states
Always show users what they're missing and how to get it! Clear upgrade paths convert better than hidden features.
## Common Patterns ๐ฏ
### 1. Feature Gates
```tsx
// Reusable feature gate component
function FeatureGate({
children,
requiredPlan = 'pro',
featureName = 'this feature'
}) {
const { currentPlan } = useCurrentPlan()
const hasAccess = currentPlan?.codename === requiredPlan
if (!hasAccess) {
return (
)
}
```
### 3. Plan Comparison Badge
```tsx
function PlanBadge() {
const { currentPlan } = useCurrentPlan()
if (!currentPlan) return Free
return (
{currentPlan.name}
)
}
function getPlanColor(codename: string) {
switch (codename) {
case 'pro': return 'bg-blue-100 text-blue-800'
case 'enterprise': return 'bg-purple-100 text-purple-800'
default: return 'bg-gray-100 text-gray-800'
}
}
```
## Server-Side: API Routes ๐
Enforce plan limits in API routes using `withAuthRequired`:
```ts
// src/app/api/app/advanced-feature/route.ts
import withAuthRequired from '@/lib/auth/withAuthRequired'
import { NextResponse } from 'next/server'
export const POST = withAuthRequired(async (req, context) => {
const currentPlan = await context.getCurrentPlan()
// Check boolean quota
if (!currentPlan?.quotas.advancedFeatures) {
return NextResponse.json(
{ error: 'Upgrade to Pro to access this feature' },
{ status: 403 }
)
}
// Your feature logic
const data = await req.json()
// Process...
return NextResponse.json({ success: true })
})
```
**Check numerical quotas:**
```ts
export const POST = withAuthRequired(async (req, context) => {
const currentPlan = await context.getCurrentPlan()
const userId = context.session.user.id
// Get current usage
const currentProjects = await getProjectCount(userId)
const projectLimit = currentPlan?.quotas.projects ?? 3
if (currentProjects >= projectLimit) {
return NextResponse.json(
{
error: 'Project limit reached',
current: currentProjects,
limit: projectLimit,
upgradeUrl: '/pricing'
},
{ status: 403 }
)
}
// Create project...
return NextResponse.json({ success: true })
})
```
Never rely only on client-side checks! Always validate plan access in API routes to prevent bypassing restrictions.
## Next Steps ๐
1. **Define quotas** in `src/db/schema/plans.ts`
2. **Set quota values** for each plan in super admin
3. **Add feature gates** in your components
4. **Enforce server-side** in API routes
5. **Test all plans** to ensure proper gating
You can now gate features by plan! Always show users what they're missing and how to upgrade. ๐
## Related Documentation ๐
* [Managing Plans](/payments/managing-plans) - Create and configure plans
* [Create Subscription](/payments/create-subscription) - Implement subscriptions
* [Credits System](/payments/credits-system) - Usage-based pricing
* [Super Admin](/setup/super-admin) - Manage plans as admin
# Running LTD Campaigns (/payments/running-ltd-campaigns)
YouTube Video: https://www.youtube.com/watch?v=oczhPugwt2c
Launch successful lifetime deal (LTD) campaigns with coupon-based access! Perfect for AppSumo, ProductHunt, and other deal platforms. ๐
LTDs let you sell lifetime access at discounted prices. Users redeem coupon codes to unlock plans - great for rapid user acquisition!
## How It Works ๐ฏ
The coupon system allows tiered access:
1. **Create coupon codes** with campaign prefix (e.g., `APPSUMO_ABC123`)
2. **Configure plans** to require X number of codes
3. **Users redeem codes** to unlock features
4. **Stack codes** for higher tiers (1 code = Basic, 2 codes = Pro, etc.)
**Example flow:**
* Basic Plan: Requires 1 code
* Pro Plan: Requires 2 codes
* Enterprise Plan: Requires 3 codes
Users buy multiple codes on deal platforms to unlock higher tiers! This increases your revenue per customer.
## Creating Coupon Codes โก
### Step 1: Generate Codes
Navigate to `/super-admin/coupons` โ **Create Coupon Codes**
**Configure:**
1. **Number of codes** - How many to generate (e.g., 1000 codes)
2. **Campaign prefix** - Identifier (e.g., `APPSUMO`, `BLACKFRIDAY`, `PH2024`)
3. **Generate** - Creates unique codes
**Example codes generated:**
```
APPSUMO_A1B2C3D4
APPSUMO_E5F6G7H8
APPSUMO_I9J0K1L2
...
```
Use descriptive prefixes to track which platform/campaign codes came from. Helps with analytics and support!
### Step 2: Export & Distribute
1. **Export codes** - Download CSV of all codes
2. **Send to platform** - Upload to AppSumo, ProductHunt, etc.
3. **Platform distributes** - Customers receive codes after purchase
## Configuring Plan Tiers ๐ฏ
### Set Code Requirements
Navigate to `/super-admin/plans` and edit each plan:
**Configure code requirements:**
```
Basic Plan:
- Codes required: 1
- Price on platform: $49
Pro Plan:
- Codes required: 2
- Price on platform: $98 ($49 ร 2)
Enterprise Plan:
- Codes required: 3
- Price on platform: $147 ($49 ร 3)
```
Set code requirements to create natural upgrade tiers. Users buy more codes on the platform to unlock better plans!
### Example Configuration
```tsx
// Basic Tier
{
name: "LTD Basic",
codesRequired: 1,
features: [
"10 projects",
"Basic support",
"Lifetime access"
]
}
// Pro Tier
{
name: "LTD Pro",
codesRequired: 2,
features: [
"100 projects",
"Priority support",
"Advanced features",
"Lifetime access"
]
}
// Enterprise Tier
{
name: "LTD Enterprise",
codesRequired: 3,
features: [
"Unlimited projects",
"24/7 support",
"All features",
"Lifetime access"
]
}
```
## User Redemption Flow ๐ซ
### How Users Redeem
1. **Purchase on platform** - Buy 1-3 codes on AppSumo/etc
2. **Receive codes** - Get unique coupon codes
3. **Redeem in app** - Enter codes at `/redeem` or `/app/billing`
4. **Instant access** - Plan activated immediately
5. **Stack for upgrades** - Buy more codes to upgrade tier
### Redemption Page
Users visit your redemption page:
```tsx
// app/redeem/page.tsx
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
export default function RedeemPage() {
const [code, setCode] = useState('')
const [loading, setLoading] = useState(false)
const handleRedeem = async () => {
setLoading(true)
const res = await fetch('/api/redeem-coupon', {
method: 'POST',
body: JSON.stringify({ code }),
})
if (res.ok) {
alert('Code redeemed! Plan activated.')
window.location.href = '/app'
} else {
alert('Invalid or expired code')
}
setLoading(false)
}
return (
Redeem Your Code
Enter your LTD coupon code below:
setCode(e.target.value.toUpperCase())}
/>
)
}
```
## Managing Active Campaigns ๐
### View Campaign Stats
Navigate to `/super-admin/coupons` to see:
* **Total codes generated**
* **Codes redeemed**
* **Codes remaining**
* **Revenue per campaign**
* **Active users per tier**
### Expire Campaigns
When campaign ends:
1. Navigate to `/super-admin/coupons`
2. Find your campaign
3. Click **"Expire Coupon Codes"**
4. Confirm action
When codes expire, users are automatically downgraded to free/default plan! Warn users before expiring if needed.
**What happens:**
* โ Codes become invalid
* โ New redemptions blocked
* โ Existing users keep access (or downgraded, configurable)
* โ Campaign closed
## LTD Campaign Best Practices ๐ก
### 1. Pricing Strategy
```tsx
// Calculate your LTD pricing
const monthlyPrice = 29
const yearlyValue = monthlyPrice * 12 // $348
const ltdPrice = 49 // Per code
// Break-even analysis
const breakEven = ltdPrice / monthlyPrice // 1.7 months
```
**Strategy:** Price LTD at 1-3 months of subscription value.
### 2. Tier Structure
**Option A: Linear Pricing**
* 1 code: $49 (Basic)
* 2 codes: $98 (Pro)
* 3 codes: $147 (Enterprise)
**Option B: Volume Discount**
* 1 code: $49 (Basic)
* 2 codes: $89 (Pro) - Save $9!
* 3 codes: $119 (Enterprise) - Save $28!
### 3. Feature Differentiation
Make tier upgrades valuable:
```tsx
Basic (1 code):
- Core features
- 10 projects
- Email support
Pro (2 codes):
- All Basic features
- 100 projects
- Priority support
- Advanced features
Enterprise (3 codes):
- All Pro features
- Unlimited projects
- 24/7 support
- API access
- Custom integrations
```
### 4. Communication
**Before campaign:**
* Announce on social media
* Email existing users
* Prepare support docs
* Test redemption flow
**During campaign:**
* Monitor redemptions
* Provide quick support
* Share success stories
* Encourage upgrades
**After campaign:**
* Thank participants
* Share user wins
* Close redemptions
* Plan next campaign
## Troubleshooting ๐ง
**Code not working?**
* Check if code exists in database
* Verify code hasn't been used
* Check if campaign expired
* Look for typos (codes are case-sensitive)
**User can't upgrade?**
* Check how many codes they've redeemed
* Verify plan code requirements
* Check if they have remaining codes
* Review redemption history
**Need to refund?**
* Mark code as unused in admin
* Code becomes available again
* User can redeem elsewhere
* Or generate replacement code
## Platform-Specific Tips ๐ฏ
### AppSumo
* Use prefix `APPSUMO_`
* Typical: 3-tier structure
* Price per code: $49-79
* Campaign duration: 2-4 weeks
### ProductHunt
* Use prefix `PH_` or `PRODUCTHUNT_`
* Launch day special
* Limited quantity creates urgency
* Price per code: $39-59
### Your Own Site
* Use prefix `LAUNCH_` or `EARLY_`
* Control everything
* Email delivery
* Custom tiers
Always generate 20-30% more codes than you expect to sell. Better to have extras than run out during a campaign!
## Next Steps ๐
1. **Create first campaign** - Generate codes with prefix
2. **Configure plan tiers** - Set code requirements
3. **Test redemption** - Use test code to verify flow
4. **Launch campaign** - Submit to AppSumo/PH
5. **Monitor & support** - Track redemptions, help users
Your coupon system is ready for LTD campaigns! Start with AppSumo or ProductHunt to acquire users fast. ๐
## Related Documentation ๐
* [One-Time Payments](/payments/create-one-time-payment) - Alternative LTD approach
* [Managing Plans](/payments/managing-plans) - Configure plans
* [Super Admin](/setup/super-admin) - Admin access
# Blog Posts (/seo/create-blog-post)
YouTube Video: https://www.youtube.com/watch?v=cIDhoIN_Dn0
Indie Kit comes with a built-in blog system that supports MDX, tags, featured images, and automatic routing. Let's learn how to create beautiful blog posts! โจ
## Quick Start ๐
1. Rename the sample blog post file from:
```
src/content/blog/add-button-to-any-website.mdx.donotuse
```
to:
```
src/content/blog/add-button-to-any-website.mdx
```
2. Your blog will automatically appear at `/blog/add-button-to-any-website`!
## Blog Post Structure ๐
Each blog post is an MDX file with frontmatter metadata. Here's the structure:
```mdx
---
title: Your Blog Title
tags: ["tag1", "tag2", "tag3"]
featuredImage: '/assets/images/your-image.png'
createdDate: '2024-03-20'
description: 'A brief description of your blog post'
---
# Your Content Here
Write your blog content using Markdown and MDX!
```
### Frontmatter Fields ๐
* `title`: The title of your blog post
* `tags`: Array of tags for categorizing your post
* `featuredImage`: Path to the featured image (stored in `public` directory)
* `createdDate`: Publication date (YYYY-MM-DD format)
* `description`: Brief description for SEO and previews
## Writing Content โ๏ธ
Your blog posts support:
* ๐ Full Markdown syntax
* ๐จ MDX components
* ๐ Code blocks with syntax highlighting
* ๐ผ๏ธ Images and media
* ๐ Automatic table of contents
* ๐ Internal and external links
### Example Content Structure
```mdx
# Main Title
## Section 1
Content for section 1...
### Subsection 1.1
More detailed content...
## Section 2
Another section with content...
```
## Best Practices ๐ก
1. **Images**
* Store images in `public/assets/images/`
* Use descriptive file names
* Optimize images for web
2. **Tags**
* Use relevant, consistent tags
* Keep tags lowercase
* Use hyphen for multi-word tags
3. **SEO**
* Write descriptive titles
* Include meaningful descriptions
* Use relevant tags
* Add alt text to images
## Viewing Your Blog ๐
* Individual posts: `/blog/[slug]`
* Blog listing: `/blog`
* Tags are automatically grouped
* Featured images appear in previews
The blog system will automatically:
* Generate SEO metadata
* Create a table of contents
* Handle routing
* Show related posts
* Enable social sharing
Now you're ready to start blogging! Create engaging content and share it with your audience! ๐
# Google Search Console (/seo/google-search-console)
# Google Search Console ๐
Google Search Console is essential for monitoring your site's performance in Google Search! Let's get you set up. ๐
## Why You Need This ๐ฏ
Google Search Console helps you:
* ๐ **Monitor Search Performance**: See how many people find your site through Google
* ๐ **Track Keywords**: Discover which search terms bring visitors to your site
* ๐ **Fix Issues**: Get alerts about errors, broken links, or indexing problems
* ๐ **Submit Sitemaps**: Help Google discover and index all your pages
* โก **Improve Rankings**: Get insights to optimize your content for better visibility
It's completely free and takes just 5 minutes to set up!
## Prerequisites โ
Before you start:
* โ Your Indie Kit site must be deployed and live
* โ You need a Google account
* โ You should be able to modify your site's code or DNS settings
## Step 1: Add Your Property ๐
1. Go to [Google Search Console](https://search.google.com/search-console)
2. Click **"Start now"** or **"Add property"** if you already have an account
3. You'll see two options - choose **URL prefix**:
### Choosing Property Type ๐ค
**URL-prefix property** (Recommended) โจ
* Example: `https://yourdomain.com`
* Includes only URLs with the exact prefix you specify
* Multiple verification methods available
* **Best for most users!**
**Domain property**
* Example: `yourdomain.com`
* Includes all subdomains (www, m, etc.) and protocols (http, https)
* Requires DNS verification (more technical)
* Good if you have multiple subdomains
For most Indie Kit apps, use **URL-prefix property** with your full URL including `https://`
4. Enter your site URL: `https://yourdomain.com`
5. Click **Continue**
## Step 2: Verify Ownership โ
Google needs to confirm you own the site. Choose one of these verification methods:
### Option 1: HTML Tag (Easiest for Indie Kit) ๐ท๏ธ
1. Google will show you a meta tag like:
```html
```
2. Add it to your Indie Kit app's `src/app/layout.tsx`:
```tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
3. Deploy your changes
4. Go back to Google Search Console and click **Verify**
### Option 2: HTML File Upload ๐
1. Download the HTML file Google provides
2. Upload it to your `public/` folder
3. Deploy your changes
4. Click **Verify**
### Option 3: DNS Record (For Domain Property) ๐
1. Copy the TXT record Google provides
2. Add it to your domain's DNS settings (in your domain registrar)
3. Wait a few minutes for DNS to propagate
4. Click **Verify**
Verification may take a few minutes after deployment. If it fails, wait 5-10 minutes and try again!
## Step 3: Submit Your Sitemap ๐บ๏ธ
Once verified, submit your sitemap so Google can discover all your pages:
1. In Google Search Console, go to **Sitemaps** (left sidebar)
2. Enter your sitemap URL: `sitemap.xml`
3. Click **Submit**
Indie Kit automatically generates your sitemap! It's available at `https://yourdomain.com/sitemap.xml`. Learn more in the [Sitemap documentation](/seo/sitemap).
## What Happens Next? โฐ
* **Verification**: Takes a few minutes to a few hours
* **Data Collection**: Starts appearing in 2-3 days
* **Full Data**: Takes about a week to show complete statistics
Don't worry if you don't see data immediately - this is normal! ๐
## Monitoring Your Site ๐
Once data starts coming in, check these sections regularly:
### Performance Report
* See clicks, impressions, and average position
* Discover which keywords bring traffic
* Identify top-performing pages
### Coverage Report
* Check which pages are indexed
* Find and fix errors
* Submit new pages for indexing
### Enhancements
* Check mobile usability
* Monitor Core Web Vitals
* Fix structured data issues
## Troubleshooting ๐ง
**"Verification failed"**
* Make sure your site is deployed with the verification code
* Wait 5-10 minutes and try again
* Clear your browser cache
**"No data available"**
* Normal for new sites - wait 2-3 days
* Ensure your sitemap is submitted
* Check that your site is publicly accessible
**"Sitemap couldn't be read"**
* Verify your sitemap URL: `https://yourdomain.com/sitemap.xml`
* Check that your site is live
* Wait a few minutes and resubmit
## Best Practices ๐ก
1. **Check Weekly**: Review your performance report weekly
2. **Fix Errors**: Address coverage errors promptly
3. **Submit New Content**: Manually submit important new pages via "URL Inspection"
4. **Monitor Core Web Vitals**: Keep an eye on performance metrics
5. **Track Keywords**: Identify opportunities to create more content
## Next Steps ๐
* โ Set up email notifications for critical issues
* โ Connect Google Analytics (if you use it)
* โ Review your performance report weekly
* โ Submit your sitemap
* โ Use "URL Inspection" tool to check specific pages
Congratulations! Your site is now connected to Google Search Console. Data will start appearing in a few days! ๐
## Useful Resources ๐
* [Google Search Console Official Guide](https://support.google.com/webmasters/answer/34592)
* [Understanding Performance Report](https://support.google.com/webmasters/answer/7576553)
* [Sitemap Documentation](/seo/sitemap)
* [SEO Best Practices](/seo/seo)
# SEO Best Practices (/seo/seo)
# SEO Best Practices ๐
Indie Kit comes with built-in SEO optimization! Here's how to add metadata and structured data to your pages. โจ
## Adding Metadata to Pages ๐
Add this to any page to optimize it for search engines and social sharing:
```tsx
import { Metadata } from "next";
import { appConfig } from "@/lib/config";
export const metadata: Metadata = {
title: `About Us | ${appConfig.projectName}`,
description: "Learn more about our company, mission, and values.",
openGraph: {
title: `About Us | ${appConfig.projectName}`,
description: "Learn more about our company, mission, and values.",
images: [`${process.env.NEXT_PUBLIC_APP_URL}/images/og.png`],
},
twitter: {
card: "summary_large_image",
title: `About Us | ${appConfig.projectName}`,
description: "Learn more about our company, mission, and values.",
},
};
```
* Keep titles under 60 characters
* Keep descriptions between 150-160 characters
* Always set Open Graph images (1200x630px)
## Structured Data (JSON-LD) ๐ท๏ธ
Help search engines understand your content better with JSON-LD:
### Article/Blog Post
```tsx
import { ArticleJsonLd } from "next-seo";
```
### Product
```tsx
import { ProductJsonLd } from "next-seo";
```
### FAQ
```tsx
import { FAQPageJsonLd } from "next-seo";
```
## Sitemap & Robots.txt ๐บ๏ธ
Your sitemap is automatically generated! Learn more about [customizing your sitemap](/seo/sitemap).
## Quick SEO Checklist โ
Before launching, make sure you:
* โ Add metadata to all public pages
* โ Set Open Graph images (1200x630px)
* โ Submit sitemap to [Google Search Console](https://search.google.com/search-console)
* โ Test your pages with [Rich Results Test](https://search.google.com/test/rich-results)
* โ Verify social previews with [OpenGraph.xyz](https://www.opengraph.xyz/)
* โ Check mobile-friendliness
## Testing Tools ๐ ๏ธ
**Essential Tools:**
* [Google Search Console](https://search.google.com/search-console) - Submit sitemap & monitor indexing
* [Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data
* [OpenGraph.xyz](https://www.opengraph.xyz/) - Preview social cards
* [Detailed SEO Extension](https://chromewebstore.google.com/detail/detailed-seo-extension/pfjdepjjfjjahkjfpkcgfmfhmnakjfba) - Chrome extension for quick audits
That's it! Your Indie Kit app is now SEO-ready! ๐
# Sitemap (/seo/sitemap)
# Sitemap
Great news! Indie Kit automatically handles sitemap generation for your application! ๐
Your sitemap is automatically created and accessible at `/sitemap.xml`. It includes all your static pages, blog posts, policy pages, and documentation pages without any setup required.
## Why Sitemaps Matter ๐ฏ
Having a sitemap is crucial for SEO because it:
* ๐ **Helps Search Engines Discover Content**: Ensures all your pages are found and indexed by search engines like Google, Bing, and others
* โก **Speeds Up Indexing**: New pages and updates are discovered faster, improving your site's visibility
* ๐ **Improves Crawl Efficiency**: Tells search engines which pages are most important and how often they change
* ๐บ๏ธ **Provides Structure**: Gives search engines a clear map of your site's architecture and content hierarchy
* ๐ **Boosts Rankings**: Better indexing means better chances of ranking for relevant searches
* ๐ **Prioritizes Fresh Content**: Helps search engines know when content is updated, especially for blogs and dynamic pages
Without a sitemap, search engines might miss important pages, leading to lower visibility and reduced organic traffic.
## What's Included by Default ๐ฆ
Indie Kit's sitemap automatically includes:
* โ Static pages (home, about, contact, pricing, etc.)
* โ Policy pages (privacy, terms, refund, cookie)
* โ Blog posts (with creation dates)
* โ Documentation pages
* โ Proper metadata (lastModified, changeFrequency, priority)
## How It Works ๐ง
Indie Kit's sitemap generation is handled automatically through `src/app/sitemap.ts`. The system:
* ๐ Collects all static pages (home, about, contact, etc.) with appropriate priority levels
* ๐ Gathers policy pages (privacy, terms, refund, cookie) with standard metadata
* ๐ Fetches all blog posts with their creation dates
* ๐ Includes all documentation pages from your docs directory
* โ๏ธ Combines everything with proper metadata (lastModified, changeFrequency, priority)
* ๐ Automatically regenerates when you deploy or add new content
Everything works out of the box! Your sitemap is automatically generated and kept up-to-date without any manual intervention.
## Customizing Your Sitemap ๐จ
Want to add your own pages? Simply update `src/app/sitemap.ts`:
```typescript
// Add your custom pages to the staticPages array
const staticPages = [
"",
"/about",
"/contact",
"/join-waitlist",
"/blog",
"/pricing",
"/features", // โจ Your new page
"/testimonials", // โจ Your new page
"/case-studies", // โจ Your new page
].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: "monthly" as const,
priority: route === "" ? 1 : 0.8,
}));
```
## Programmatic SEO (pSEO) ๐
Want to include database items in your sitemap? This is perfect for programmatic SEO! Here's an example with a "projects" database:
```typescript
import { MetadataRoute } from "next";
import { getAllBlogs } from "@/lib/mdx/blogs";
import { db } from "@/db"; // Your database instance
import { projects } from "@/db/schema"; // Your projects table
export default async function sitemap(): Promise {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL!;
const blogs = await getAllBlogs();
// Fetch all projects from database
const allProjects = await db.select().from(projects);
// Static pages
const staticPages = [
"",
"/about",
"/contact",
"/projects", // Projects listing page
].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: "monthly" as const,
priority: route === "" ? 1 : 0.8,
}));
// Blog pages
const blogPages = blogs.map((blog) => ({
url: `${baseUrl}/blog/${blog.slug}`,
lastModified: new Date(blog.frontmatter.createdDate),
changeFrequency: "weekly" as const,
priority: 0.6,
}));
// ๐ฏ Project pages from database
const projectPages = allProjects.map((project) => ({
url: `${baseUrl}/projects/${project.slug}`,
lastModified: project.updatedAt || project.createdAt,
changeFrequency: "weekly" as const,
priority: 0.7,
}));
return [...staticPages, ...blogPages, ...projectPages];
}
```
## Generating Multiple Sitemaps ๐
For large sites with 50,000+ URLs, you can generate multiple sitemaps:
```typescript
import type { MetadataRoute } from 'next'
import { BASE_URL } from '@/app/lib/constants'
export async function generateSitemaps() {
// Fetch the total number of products and calculate the number of sitemaps needed
return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
}
export default async function sitemap({
id,
}: {
id: number
}): Promise {
// Google's limit is 50,000 URLs per sitemap
const start = id * 50000
const end = start + 50000
const products = await getProducts(
`SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
)
return products.map((product) => ({
url: `${BASE_URL}/product/${product.id}`,
lastModified: product.date,
}))
}
```
This generates multiple sitemaps like:
* `/sitemap/0.xml`
* `/sitemap/1.xml`
* `/sitemap/2.xml`
* etc.
## Customizing Robots.txt ๐ค
To control search engine crawlers, update `src/app/robots.ts`:
```typescript
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin/', '/api/'],
},
{
userAgent: 'Googlebot',
allow: '/',
crawlDelay: 2,
},
],
sitemap: `${process.env.NEXT_PUBLIC_APP_URL}/sitemap.xml`,
}
}
```
You can customize rules for specific user agents. This is useful for controlling how different search engines crawl your site!
## Best Practices ๐ก
1. **Priority Values**
* Homepage: `1.0` (highest priority)
* Main pages: `0.8`
* Blog posts: `0.6-0.7`
* Policy pages: `0.5`
2. **Change Frequency**
* Static pages: `monthly`
* Blog posts: `weekly`
* Dynamic content: `daily` or `weekly`
3. **Last Modified**
* Use actual creation/update dates for blog posts
* Use `new Date()` for static pages
* Keep database items updated with their modification dates
4. **Testing**
* Visit `/sitemap.xml` to verify your sitemap
* Check that all expected URLs are included
* Validate with [Google Search Console](https://search.google.com/search-console)
## Resources ๐
Learn more about sitemaps in Next.js:
* [Next.js Sitemap Documentation](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap)
* [Generating Multiple Sitemaps](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generating-multiple-sitemaps)
* [Robots.txt Configuration](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots#customizing-specific-user-agents)
Your sitemap is automatically generated and ready to help search engines discover your content! ๐
# Background Jobs (/setup/background-jobs)
YouTube Video: https://www.youtube.com/watch?v=gz2331T2sAw
Run background tasks reliably! Perfect for email sequences, scheduled jobs, and async processing powered by Inngest. โก
Background jobs work out of the box in development! No configuration needed for local testing.
## What You Can Build ๐ฏ
With background jobs, you can:
* โฐ **Scheduled tasks** - Cron jobs, cleanup tasks
* ๐ง **Email sequences** - Onboarding flows, drip campaigns
* ๐ **Retry logic** - Automatic retry on failures
* ๐ **Analytics** - Process data in background
* ๐ค **Webhooks** - Handle external events
## Local Development ๐ป
Your local Inngest server runs automatically at:
```
http://localhost:8288/stream
```
**Monitor your jobs:**
1. Start your dev server: `pnpm dev`
2. Open `http://localhost:8288`
3. See all jobs and their status
4. Test and debug locally
The local Inngest UI shows execution history, logs, and errors. Perfect for debugging! ๐
## Production Setup ๐
### Step 1: Create Inngest Account
1. Sign up at [Inngest.com](https://www.inngest.com/) (free tier available!)
2. Create a new app in dashboard
3. Copy your `INNGEST_EVENT_KEY`
### Step 2: Add to Environment Variables
```bash
INNGEST_EVENT_KEY="your-key-here"
```
Add this to your hosting platform's environment variables, not to `.env` in your repo!
### Step 3: Deploy (Platform-Specific)
Choose your hosting platform:
**๐ข Vercel (Recommended)**
1. Add `INNGEST_EVENT_KEY` to environment variables
2. Deploy your app
3. Install [Inngest integration](https://www.inngest.com/docs/deploy/vercel) from Vercel marketplace
4. Jobs automatically sync!
Vercel + Inngest integration handles everything automatically. Recommended for easiest setup! โญ
**๐ฃ Render**
1. Add `INNGEST_EVENT_KEY` to environment variables
2. Deploy your app
3. Configure webhook URL in Inngest dashboard
4. [Full Render guide โ](https://www.inngest.com/docs/deploy/render)
**๐ต Netlify**
1. Add `INNGEST_EVENT_KEY` to environment variables
2. Enable Netlify Functions
3. Configure webhook URL in Inngest dashboard
4. [Full Netlify guide โ](https://www.inngest.com/docs/deploy/netlify)
## Verify Setup โ
After deploying, confirm everything works:
1. **Check Inngest Dashboard**
* Visit [app.inngest.com](https://app.inngest.com)
* See your functions listed under "Functions"
* All jobs from your code appear automatically
2. **Test a Job**
* Trigger a test job (e.g., send email)
* Watch execution in dashboard
* Check logs and status
Inngest automatically discovers your functions from your code. No manual registration needed! โจ
## Example: Email Sequence ๐ง
Here's a quick example of a background job:
```ts
// src/lib/inngest/functions/welcome-email.ts
import { inngest } from "../client";
import { sendEmail } from "@/lib/email";
export const welcomeEmailSequence = inngest.createFunction(
{ id: "welcome-email-sequence" },
{ event: "user/signup" },
async ({ event, step }) => {
// Send immediately
await step.run("send-welcome", async () => {
await sendEmail({
to: event.data.email,
subject: "Welcome! ๐",
template: "welcome"
});
});
// Wait 3 days
await step.sleep("wait-3-days", "3d");
// Send follow-up
await step.run("send-tips", async () => {
await sendEmail({
to: event.data.email,
subject: "Here are some tips ๐ก",
template: "tips"
});
});
}
);
```
**Trigger the job:**
```ts
// When user signs up
await inngest.send({
name: "user/signup",
data: { email: user.email }
});
```
## Built-in Features ๐
Inngest handles complex job management:
* โฐ **Scheduling** - Run jobs at specific times
* ๐ **Retries** - Automatic retry on failures
* ๐ **Monitoring** - Real-time execution tracking
* ๐จ **Error alerts** - Get notified of failures
* ๐ **Throttling** - Control job execution rates
* โธ๏ธ **Delays** - Wait between steps
* ๐ฏ **Conditional logic** - Branch based on results
## Best Practices ๐ก
**1. Test Locally First**
```bash
pnpm dev # Inngest dev server starts automatically
```
**2. Use Step Functions**
```ts
// โ Good - Each step is retryable
await step.run("fetch-data", async () => fetchData());
await step.run("process", async () => process());
// โ Bad - All-or-nothing
await fetchData();
await process();
```
**3. Handle Errors Gracefully**
```ts
try {
await step.run("send-email", async () => {
await sendEmail();
});
} catch (error) {
// Log and handle
console.error("Failed to send:", error);
throw error; // Triggers retry
}
```
**4. Keep Jobs Idempotent**
* Jobs might run multiple times
* Use unique IDs to prevent duplicates
* Check state before performing actions
## Next Steps ๐
1. **Explore examples** in `src/lib/inngest/functions/`
2. **Create your first job** - Email sequence or scheduled task
3. **Test locally** - Use the dev UI at localhost:8288
4. **Deploy** - Push and see jobs in Inngest dashboard
You can now run reliable background tasks! Start with a simple email sequence and build from there. โก
## Related Documentation ๐
* [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns
* [Create Email Sequence](/background-jobs/create-email-sequence) - Drip campaigns
* [Inngest Docs](https://www.inngest.com/docs) - Full platform docs
# Crisp Chat (/setup/chat)
# Crisp Chat Support ๐ฌ
Indie Kit includes seamless integration with Crisp Chat, allowing you to provide real-time support to your users.
## Configuration ๐ ๏ธ
To enable Crisp Chat, add your website ID to your `.env` file:
```bash
NEXT_PUBLIC_CRISP_WEBSITE_ID=your_website_id_here
```
## Features โจ
### Automatic Setup
Indie Kit automatically initializes the chat widget when the environment variable is present.
### User Authentication
The integration automatically handles user authentication. When a user is logged in, their name and email will be automatically synced with Crisp, so you know exactly who you're talking to.
## Customization ๐จ
If you need to customize the chat behavior or appearance further, you can modify the component at:
`src/components/chat/crisp.tsx`
Feel free to update this file to match your specific requirements or to add advanced Crisp features.
# Database (/setup/database)
import { Tabs, Tab } from 'fumadocs-ui/components/tabs'
import Image from 'next/image'
# Setting Up Your Database โก๏ธ
Indie Kit supports multiple database providers. Choose the one that best fits your needs! ๐
## Setting up Neon (PostgreSQL) ๐ฏ
1. **Create a Neon Account** ๐ค
* Go to [neon.tech](https://neon.tech)
* Click "Sign Up" and create your account
* You can use GitHub for quick signup
2. **Create a New Project** ๐ฆ
* Click "New Project" in your Neon dashboard
* Choose a name for your project
* Select the region closest to your users
* Click "Create Project"
3. **Get Your Connection String** ๐
```bash
# Your connection string will look like this:
postgresql://[user]:[password]@[neon-host]/[database]
```
* Find the connection string in your project dashboard
* Make sure to use the full connection string with your credentials
Neon offers a generous free tier perfect for development and small projects. You can always upgrade as your project grows!
## Setting up Supabase (PostgreSQL) ๐ฏ
1. **Create a Supabase Account** ๐ค
* Go to [supabase.com](https://supabase.com)
* Click "Start your project"
* Sign up with GitHub for easier setup
2. **Create a New Project** ๐ฆ
* Click "New Project" in your dashboard
* Fill in your project details
* Choose a region and database password
* Wait for project creation (usually 1-2 minutes)
3. **Get Your Connection String** ๐
* Click "Connect"
```bash
postgresql://postgres:[YOUR-PASSWORD]@db.[project-ref].supabase.co:5432/postgres
```
Supabase provides additional features like Auth, Storage, and Edge Functions! Perfect if you need a full backend platform.
## Setting up PlanetScale (MySQL) ๐ฏ
1. **Create a PlanetScale Account** ๐ค
* Visit [planetscale.com](https://planetscale.com)
* Click "Sign Up"
* Create your account (GitHub recommended)
2. **Create a New Database** ๐ฆ
* Click "Create a new database"
* Choose a name and region
* Select "Hobby" plan for development
* Click "Create database"
3. **Get Your Connection String** ๐
* Click "Connect"
* Choose "Connect with" > "Prisma"
* Copy the connection string
```bash
mysql://[username]:[password]@[host]/[database]?sslaccept=strict
```
PlanetScale's branching feature is great for schema changes! Perfect for teams with complex database workflows.
## Configuring Your Project ๐ ๏ธ
1. **Set Up Environment Variables** ๐
* Open your `.env` file
* Add your database connection string:
```bash
DATABASE_URL="your-connection-string-here"
```
## Syncing Database Schema ๐
2. **Push Schema to Database** ๐
```bash
npx drizzle-kit push
```
This command will:
* Connect to your database
* Create all necessary tables
* Sync your schema changes
3. **Verify Setup** โ
```bash
npm run dev
```
* Start your development server
* Your app should now connect to the database
* Check the console for any connection messages
Indie Kit uses "JWT" session strategy by default, though you can use "Database" session strategy if you want to. Read more about it [here](https://authjs.dev/concepts/session-strategies).
If you want to use your own hosted PostgreSQL database, you may need to set the following in `db.ts` file:
```typescript
import { drizzle } from 'drizzle-orm/postgres-js';
export const db = drizzle(process.env.DATABASE_URL!)
```
## Troubleshooting ๐
If you encounter any issues:
1. **Connection Issues** ๐
* Verify your connection string is correct
* Check if your IP is allowed in database settings
* Ensure your database user has proper permissions
2. **Schema Sync Issues** ๐
* Check if `drizzle.config.ts` is properly configured
* Verify your schema files are correctly exported
* Look for any console errors during schema push
## Next Steps ๐ฏ
* Set up your database indexes for better performance
* Configure database pooling if needed
* Consider setting up database backups
* Explore your database provider's dashboard for monitoring
Now your Indie Kit app is connected to your chosen database! Ready to start building? ๐
# Legal (/setup/policies)
YouTube Video: https://www.youtube.com/watch?v=E2bse4u4D58
Let's set up policies and contact form for your Indie Kit application! ๐
# Sentry (/setup/sentry)
# Sentry Support ๐ก๏ธ
Indie Kit comes with built-in support for Sentry to help you track errors and monitor your application's performance.
## Configuration ๐ ๏ธ
To enable Sentry, you simply need to add the following environment variables to your `.env` file:
```bash
NEXT_PUBLIC_SENTRY_DSN=your_dsn_here
SENTRY_PROJECT=your_project_name
SENTRY_AUTH_TOKEN=your_auth_token
```
Once these are set, Indie Kit will automatically configure Sentry for your application.
## Inngest Integration โก๏ธ
Indie Kit also automatically sets up Sentry for Inngest. No additional configuration is required!
## Troubleshooting ๐
If Sentry isn't reporting errors:
1. Verify your `NEXT_PUBLIC_SENTRY_DSN` is correct.
2. Check your Sentry project settings to ensure the domain is allowed.
3. Make sure you've rebuilt your application after adding the environment variables.
# Super Admin (/setup/super-admin)
YouTube Video: https://www.youtube.com/watch?v=fOOcvlVc7Sc
Get admin access to your platform! Manage users, plans, waitlists, and more from the super admin dashboard. ๐
Make sure you have [database](/setup/database) and [authentication](/setup/auth) configured first.
## Super Admin Powers ๐ฆธ
With super admin access, you can:
* ๐ฅ **Manage Users** - View, edit, impersonate users
* ๐ณ **Manage Plans** - Create and edit subscription plans
* ๐ **Waitlist Management** - View and export waitlist signups
* ๐ **Analytics** - Platform-wide stats and insights
* ๐ซ **User Credits** - Add or remove credits manually
* โ๏ธ **Platform Settings** - Configure app-wide settings
## Setup (30 seconds) โก
### Step 1: Add Admin Emails
Add to `.env.local`:
```bash
# Single admin
SUPER_ADMIN_EMAILS=admin@yourapp.com
# Multiple admins (comma-separated)
SUPER_ADMIN_EMAILS=admin@yourapp.com,manager@yourapp.com,support@yourapp.com
# Enable signin
NEXT_PUBLIC_SIGNIN_ENABLED=true
```
Make sure to use the email address you'll sign in with! Case-sensitive matching.
### Step 2: Restart Server
```bash
# Stop and restart
pnpm dev
```
### Step 3: Sign In
1. Sign in with your admin email
2. Visit `/super-admin`
3. See the admin dashboard! ๐
You now have full admin access to your platform! ๐
## Admin Dashboard Features ๐ฏ
### User Management
**What you can do:**
* View all users with search and filters
* See user activity and subscription status
* Impersonate users (see app from their perspective)
* Manually adjust user data
* Add/remove credits
**Access:** `/super-admin/users`
### Plan Management
**What you can do:**
* Create new subscription plans
* Edit existing plans (pricing, features, quotas)
* Set default plans for new signups
* Configure payment provider IDs
* Archive old plans
**Access:** `/super-admin/plans`
Existing subscriptions aren't affected when you edit plans. Users on old plans keep their pricing until they change plans.
### Waitlist Management
**What you can do:**
* View all waitlist signups
* Export to CSV
* Send bulk emails
* Track signup sources
**Access:** `/super-admin/waitlist`
### Contact Form Submissions
**What you can do:**
* View all contact form submissions
* Mark as resolved
* Respond to inquiries
* Export submissions
**Access:** `/super-admin/contact`
## Security Best Practices ๐
**1. Use Strong Emails**
```bash
# โ Good - Work emails you control
SUPER_ADMIN_EMAILS=founder@startup.com,cto@startup.com
# โ Bad - Personal emails that might change
SUPER_ADMIN_EMAILS=myemail@gmail.com
```
**2. Limit Admin Access**
* Only add trusted team members
* Remove admin emails when people leave
* Review admin list regularly
**3. Monitor Admin Actions**
* Check audit logs (if enabled)
* Monitor for unusual activity
* Set up alerts for critical changes
**4. Production Environment**
```bash
# In production .env
SUPER_ADMIN_EMAILS=secure@yourdomain.com
NEXT_PUBLIC_SIGNIN_ENABLED=true
```
Remember to set `SUPER_ADMIN_EMAILS` in your production environment variables! Don't rely on `.env` file.
## User Impersonation ๐ญ
One powerful feature is user impersonation:
**How to use:**
1. Go to `/super-admin/users`
2. Find the user you want to impersonate
3. Click "Impersonate"
4. See the app exactly as they see it
5. Click "Exit Impersonation" when done
**Use cases:**
* ๐ Debug user-reported issues
* ๐ See what users experience
* ๐ Help users navigate the app
* ๐งช Test permissions and roles
Always inform users if you'll be accessing their account. Respect privacy and use only for support/debugging!
## Common Tasks ๐
### Add a New Admin
```bash
# Edit .env.local
SUPER_ADMIN_EMAILS=existing@admin.com,new@admin.com
```
Restart server. New admin can access `/super-admin` immediately.
### Remove Admin Access
```bash
# Edit .env.local - remove their email
SUPER_ADMIN_EMAILS=remaining@admin.com
```
Restart server. Removed admin loses access.
### Check Who Has Access
```bash
# View current admins
echo $SUPER_ADMIN_EMAILS
```
### Give Temporary Access
For contractors or temporary help:
1. Add their email to `SUPER_ADMIN_EMAILS`
2. Set a reminder to remove later
3. Remove when contract ends
## Troubleshooting ๐ง
**Can't access /super-admin?**
* โ Check your email is in `SUPER_ADMIN_EMAILS`
* โ Verify email matches exactly (case-sensitive)
* โ Restart dev server after adding email
* โ Sign out and back in
**Changes not reflecting?**
* Restart your dev server
* Clear browser cache
* Check environment variables loaded correctly
**Multiple admins not working?**
* Use commas, no spaces: `admin1@app.com,admin2@app.com`
* Check for typos in email addresses
* Verify `.env.local` is being read
## Next Steps ๐
Now that you're a super admin:
1. **Create your plans** at `/super-admin/plans`
2. **Review waitlist** at `/super-admin/waitlist`
3. **Check user activity** at `/super-admin/users`
4. **Explore features** in the admin dashboard
You have full control over your platform! Use your powers wisely. ๐โจ
## Related Documentation ๐
* [User Impersonation](/super-admin/user-impersonation) - Detailed guide
* [Managing Plans](/payments/managing-plans) - Plan configuration
* [Waitlist Management](/super-admin/waitlist) - Waitlist features
# Contact Form Management (/super-admin/contact-us)
YouTube Video: https://www.youtube.com/watch?v=fOOcvlVc7Sc
# Contact Form Management ๐ฌ
View all contact form submissions from your super admin dashboard! Simple and straightforward. โจ
Indie Kit includes a complete contact form with admin view. No setup needed!
## How It Works ๐ฏ
**Simple flow:**
1. User fills contact form on your site
2. Submission saved to database
3. View in super admin dashboard
4. Reply to user via email
That's it! ๐
## Viewing Submissions ๐
### Navigate to Contact Dashboard
Go to `/super-admin/messages` in your admin panel.
**What you'll see:**
* ๐ง All contact form submissions
* ๐ค User name and email
* ๐ฌ Message content
* ๐ Submission date
### Example Submission
```
Name: John Doe
Email: john@example.com
Message: Can I upgrade my plan mid-month?
Submitted: Oct 16, 2025, 2:30 PM
```
Refresh the page to see new submissions!
## Responding to Users ๐ฌ
To reply to a contact submission:
1. **Note the user's email** from the dashboard
2. **Send email** using your email client
3. **Reply directly** to help them out
**Quick tip:**
```
Copy email โ Open Gmail/Outlook โ Reply
```
## Best Practices โ
**1. Check Regularly**
* Visit `/super-admin/contact` daily
* Respond within 24 hours
* Keep track of who you've replied to
**2. Use Templates**
Save time with email templates for:
* Password reset help
* Billing questions
* Feature requests
* Common questions
**3. Track Insights**
Notice patterns in submissions:
* Common questions โ Update docs
* Feature requests โ Roadmap ideas
* Bug reports โ Fix priorities
## Customization ๐จ
### Customize Contact Form
Edit the contact form:
```
src/app/(website-layout)/contact/page.tsx
```
Add custom fields like:
* Subject dropdown
* Category selection
* Phone number
* Company name
### Customize Admin View
Modify the admin view:
```
src/app/super-admin/messages/page.tsx
```
All contact form code is in your project! Customize as needed.
## Next Steps ๐
1. **Test it** - Submit a test contact
2. **Check dashboard** - View at `/super-admin/messages`
3. **Reply to users** - Use your email client
4. **Customize** - Add fields if needed
## Related Documentation ๐
* [Super Admin Setup](/setup/super-admin) - Configure admin access
* [Waitlist Management](/super-admin/waitlist) - Similar feature
* [Email Setup](/setup/email) - Configure email
# User Impersonation (/super-admin/user-impersonation)
# User Impersonation ๐ญ
See your app exactly as your users see it! Perfect for debugging and support. โจ
Only super admins can impersonate users!
## How to Impersonate ๐
Super simple! Just 3 steps:
1. **Go to super admin** - Navigate to `/super-admin/users`
2. **Click impersonate** - Find user and click the button
3. **Open in incognito** - Copy link and open in incognito/private tab
That's it! You're now seeing the app as that user. ๐
Always open the impersonation link in incognito to avoid session conflicts!
## What You Can Do ๐ฏ
While impersonating:
* โ **See their dashboard** - Exactly as they see it
* โ **Access their data** - Projects, settings, etc.
* โ **Test features** - Use the app as them
* โ **Reproduce bugs** - See errors they're experiencing
* โ **Check permissions** - Verify role-based access
## When to Use It ๐ก
**Perfect for:**
* ๐ **Debugging issues** - See what they see
* ๐ **Customer support** - Help users navigate
* ๐งช **Testing permissions** - Verify access works
* ๐ **Reproducing bugs** - See errors in context
**Example:**
```
User: "I can't see my projects!"
โ Impersonate โ See issue โ Fix bug โ
```
## Exit Impersonation ๐ช
To exit:
**Just close the incognito tab!** ๐ฏ
Your admin session in the normal browser stays safe.
## Best Practices โ
**1. Use for Support Only**
* Debug reported issues
* Reproduce bugs
* Verify feature access
**2. Keep Sessions Short**
* Exit as soon as you're done
* Close incognito tab when finished
**3. Respect Privacy**
* Use only for legitimate support
* Don't browse data without reason
Use impersonation only for support and debugging!
## Next Steps ๐
1. **Test it** - Impersonate a test user
2. **Use for support** - Debug user issues
3. **Close tab** - Exit by closing incognito
## Related Documentation ๐
* [Super Admin Setup](/setup/super-admin) - Configure admin access
* [Organization Roles](/multi-tenancy/roles) - Understanding permissions
# Waitlist Management (/super-admin/waitlist)
# Waitlist Management ๐
View all waitlist signups from your super admin dashboard! Perfect for pre-launch campaigns. โจ
Indie Kit includes a complete waitlist system with signup page and admin view. Ready to use!
## Viewing Waitlist Entries ๐
### Navigate to Waitlist Dashboard
Go to `/super-admin/waitlist` in your admin panel.
**What you'll see:**
* ๐ง All waitlist signups
* ๐ค Name and email
* ๐ Signup date
* ๐ Total signup count
### Entry Details
Each signup shows:
```
Name: Sarah Johnson
Email: sarah@example.com
Signed up: Oct 15, 2025, 3:45 PM
```
Refresh the page to see new signups!
## Email Your Waitlist ๐ง
To send updates to your waitlist:
1. **Copy emails** from the dashboard
2. **Use your email provider** (Gmail, Mailchimp, ConvertKit, etc.)
3. **Send updates** about your launch
**What to send:**
* ๐ **Welcome** - Thanks for joining!
* ๐ฐ **Updates** - Development progress
* ๐ **Launch announcement** - We're live!
* ๐๏ธ **Early access** - Beta invites
Use [email sequences](/background-jobs/create-email-sequence) to automate waitlist communications!
## Waitlist Page ๐
Users sign up at:
```
https://yourapp.com/join-waitlist
```
**Simple form:**
* Name field
* Email field
* Submit button
* Success message
### Customize Waitlist Page
Edit the page:
```
src/app/join-waitlist/page.tsx
```
Customize:
* Form fields
* Styling
* Success message
* Auto-response email
## Best Practices โ
**1. Send Welcome Email**
Set up auto-welcome email on signup to thank them!
**2. Keep Them Engaged**
* Send weekly/monthly updates
* Share progress milestones
* Build anticipation for launch
**3. Launch Strategy**
```
Week -4: Progress update
Week -2: Launch date announcement
Week -1: Final preview
Day 0: Launch! ๐
```
## Customization ๐จ
### Add Custom Fields
Edit the form to collect more data:
```tsx
// src/app/join-waitlist/page.tsx
```
All waitlist code is in your project - customize as needed!
## Next Steps ๐
1. **Test it** - Sign up yourself
2. **Check dashboard** - View at `/super-admin/waitlist`
3. **Plan launch** - Prepare email campaigns
4. **Customize** - Add fields if needed
## Related Documentation ๐
* [Launch with Waitlist](/launch-with-waitlist) - Setup guide
* [Super Admin Setup](/setup/super-admin) - Admin access
* [Email Sequences](/background-jobs/create-email-sequence) - Automate emails
# Google Login (/setup/auth/google-login)
# Setting Up Google Login ๐
Let's add Google authentication to your Indie Kit app. This guide will walk you through the setup process. ๐
## Prerequisites ๐
Before starting, make sure you've completed the [Authentication Setup](/setup/auth).
## Getting Google Credentials ๐
1. **Create Google Cloud Project** โ๏ธ
* Follow this detailed video tutorial: [Google OAuth Setup Guide](https://www.youtube.com/watch?v=OKMgyF5ezFs\&pp=ygUSZ29vZ2xlIG9hdXRoIHNldHVw)
* Go to [Google Cloud Console](https://console.cloud.google.com)
* Create a new project or select an existing one
2. **Configure OAuth Consent Screen** ๐
* Go to "APIs & Services" > "OAuth consent screen"
* Choose "External" user type
* Fill in the required information:
* App name
* User support email
* Developer contact information
3. **Create OAuth Credentials** ๐
* Go to "APIs & Services" > "Credentials"
* Click "Create Credentials" > "OAuth client ID"
* Choose "Web application"
* Add authorized redirect URIs:
```bash
# Development
http://localhost:3000/api/auth/callback/google
# Production (replace with your domain)
https://yourdomain.com/api/auth/callback/google
```
4. **Add Authorized Domains** ๐
* In the OAuth consent screen
* Add your domains:
```bash
localhost:3000
yourdomain.com
```
## Configuration โ๏ธ
5. **Add Environment Variables** ๐
Add these to your `.env.local`:
```bash
GOOGLE_CLIENT_ID=your_client_id_here
GOOGLE_CLIENT_SECRET=your_client_secret_here
```
* Client ID: Credentials page > Your OAuth 2.0 Client > Client ID
* Client Secret: Credentials page > Your OAuth 2.0 Client > Client Secret
6. **Production Setup** ๐
Add these environment variables in your Vercel dashboard:
* Go to Project Settings > Environment Variables
* Add the same variables:
```bash
GOOGLE_CLIENT_ID=your_client_id_here
GOOGLE_CLIENT_SECRET=your_client_secret_here
```
* Redeploy your application
## Verification ๐
7. **Test Local Setup**
```bash
npm run dev
```
* Visit `http://localhost:3000/sign-in`
* Click "Continue with Google"
* You should be able to sign in
8. **Test Production Setup**
* Deploy your changes
* Visit your production URL
* Verify Google login works
## Troubleshooting ๐ง
Common issues and solutions:
1. **Invalid Redirect URI**
* Double-check the exact redirect URI in Google Console
* Make sure it matches your domain exactly
* Include the full path: `/api/auth/callback/google`
2. **Unauthorized Domain**
* Verify your domain is listed in authorized domains
* Include both with and without `www` if needed
3. **Production Issues**
* Verify environment variables in Vercel
* Check if the production redirect URI is authorized
* Clear browser cache and try again
## Security Best Practices ๐ก๏ธ
1. Never commit `.env.local` to version control
2. Regularly rotate client secrets
3. Limit OAuth scope to necessary permissions
4. Monitor OAuth usage in Google Console
Your users can now sign in with their Google accounts!
## Next Steps ๐
Want to add more authentication options?
* [Add more OAuth providers](/setup/auth/z-adding-more-providers) - Facebook, GitHub, and more
* [Enable Password Login](/setup/auth/password-login) - Traditional email/password auth
# Authentication (/setup/auth)
YouTube Video: https://www.youtube.com/watch?v=2fLoibmOsVo
Setting up authentication in your Indie Kit application is quick and easy. Let's get started! ๐
## Quick Setup โก
1. Add these environment variables to your `.env` file:
```bash
# Authentication
SUPER_ADMIN_EMAILS=your.email@example.com
NEXT_PUBLIC_SIGNIN_ENABLED=true
```
2. Run the authentication setup command:
```bash
npx auth secret
```
That's it! Your authentication system is now ready to use. ๐
## Next Steps ๐
Add more authentication features to your app:
* [Set up Google Login](/setup/auth/google-login)
* [Configure Email Provider](/setup/email)
## Verifying Setup โ
To verify your authentication is working:
1. Start your development server
2. Visit `/sign-in` page
3. Try signing in with the configured provider
4. You should be redirected to the dashboard after successful login
Now your Indie Kit application is ready to handle user authentication! ๐ฏ
# Password Login (/setup/auth/password-login)
# Password-Based Authentication ๐
Let users sign in with email and password - the classic authentication method everyone knows and trusts! โจ
## What is Password Auth? ๐ค
Password authentication allows users to:
* โ Create accounts with email + password
* โ Sign in using their credentials
* โ Reset forgotten passwords via email
* โ No magic links or OAuth required
Indie Kit supports multiple auth methods! You can enable password auth alongside magic links and social logins (Google, GitHub, etc.) - or use them independently.
## When to Use Password Auth ๐ฏ
**Perfect for:**
* ๐ข **Enterprise/B2B apps** - Many businesses prefer passwords
* ๐ **Security-conscious users** - Some users trust passwords more
* ๐ฑ **Mobile-first apps** - Easier than checking email for magic links
* ๐ **International users** - Works without reliable email access
**Consider alternatives for:**
* Quick signups (magic links are faster)
* Less tech-savvy users (OAuth is simpler)
* Apps targeting developers (they love passwordless)
## Enable Password Authentication โก
Super simple - just flip a switch!
### Step 1: Update Config
Open `src/lib/config.ts` and enable password auth:
```ts
auth: {
enablePasswordAuth: true, // Enable password login
},
```
### Step 2: That's It! โจ
Your app now supports password authentication! Users will see:
* **Sign Up** - Email + password fields
* **Sign In** - Email + password login
* **Forgot Password** - Password reset flow
Everything is handled automatically! The UI, validation, password hashing, and reset flows are all built-in. Just enable and go! ๐
## What Happens Behind the Scenes ๐ง
When you enable password auth:
### Security Features ๐ก๏ธ
* โ **bcrypt Hashing** - Passwords are securely hashed, never stored plain
* โ **Salt + Pepper** - Extra security layers
* โ **Secure Reset Flow** - Time-limited password reset tokens
### User Experience โจ
* Clean, modern login UI
* Helpful validation messages
* Password confirmation on signup
* Auto-focus and keyboard shortcuts
### Email Flows ๐ง
* Welcome email on signup
* Password reset emails
* Email verification on signup
## Password Reset Flow ๐
Built-in password reset is fully functional:
**How it works:**
1. User clicks "Forgot Password"
2. Enters their email
3. Receives reset link via email
4. Clicks link, sets new password
5. Automatically logged in
**Security:**
* Reset links expire after 30 minutes
* One-time use tokens
* Email verification on signup
# User Authentication Guide (/setup/auth/user-authentication)
# User Authentication Guide ๐
Indie Kit provides powerful authentication tools that you can use throughout your application. Let's explore how to implement authentication in different contexts! ๐
## Client-Side Authentication with useUser ๐ป
The `useUser` hook is perfect for client components. It provides user data and loading states:
```tsx
'use client'
import { useUser } from '@/lib/users/useUser'
export default function ProfileButton() {
const { user, isLoading } = useUser()
if (isLoading) {
return
Loading...
}
if (!user) {
return
}
return (
{user.name}
)
}
```
### Features of useUser ๐ฏ
* ๐ Automatic revalidation
* ๐ญ Type-safe user data
* โ Loading states
* ๐ซ Error handling
* ๐ Offline support
## Server-Side Authentication with auth() ๐
For server components and API routes, use the `auth()` function:
```tsx
// src/app/profile/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function ProfilePage() {
const session = await auth()
if (!session) {
redirect('/login')
}
return (
Welcome {session.user.name}!
Email: {session.user.email}
)
}
```
### When to Use auth() ๐
* โ Server Components
* โ API Routes
* โ Server Actions
* โ Middleware
* โ Layout Components
## Protected API Routes with withAuthRequired ๐ก๏ธ
Secure your API routes using the `withAuthRequired` middleware:
```tsx
// src/app/api/app/user-settings/route.ts
import { withAuthRequired } from '@/lib/auth/withAuthRequired'
import { NextResponse } from 'next/server'
export const GET = withAuthRequired(async (req, context) => {
const { session } = context
// Access user data from session
const userId = session.user.id
// Your protected API logic here
const settings = await getUserSettings(userId)
return NextResponse.json(settings)
})
export const POST = withAuthRequired(async (req, context) => {
const { session } = context
const data = await req.json()
// Update user settings
await updateUserSettings(session.user.id, data)
return NextResponse.json({ success: true })
})
```
### Benefits of withAuthRequired ๐ฏ
* ๐ Automatic authentication checking
* ๐ญ Type-safe session data
* ๐ซ Automatic error responses
* ๐ Session context in handler
* โก Zero-config setup
## Authentication Flow Examples ๐
### Protected Dashboard Page
```tsx
// src/app/(in-app)/dashboard/page.tsx
import { auth } from '@/auth'
import { DashboardMetrics } from '@/components/dashboard/metrics'
export default async function DashboardPage() {
const session = await auth()
return (
Dashboard
)
}
```
### User Settings Component
```tsx
'use client'
import { useUser } from '@/lib/users/useUser'
import { useState } from 'react'
export function UserSettings() {
const { user, isLoading } = useUser()
const [name, setName] = useState(user?.name)
if (isLoading) return
Loading settings...
if (!user) return
Please sign in
return (
)
}
```
## Best Practices ๐ซ
1. **Choose the Right Method**
* ๐ฅ๏ธ `useUser` for client components
* ๐ `auth()` for server components
* ๐ก๏ธ `withAuthRequired` for API routes
2. **Error Handling**
* Always handle loading states
* Provide clear error messages
* Implement proper redirects
3. **Security Tips**
* Validate on both client and server
* Never expose sensitive data
* Use HTTPS in production
* Implement proper CORS policies
4. **Performance**
* Cache authentication state
* Use loading skeletons
* Implement proper revalidation
Now you're ready to implement authentication throughout your Indie Kit application! Remember to always validate user permissions and handle edge cases appropriately. ๐
# Adding More Providers (/setup/auth/z-adding-more-providers)
# Adding More Auth Providers ๐
Indie Kit supports 80+ authentication providers through Auth.js! Let's add more options for your users. ๐
## Popular Providers โจ
You can easily add any of these providers:
* ๐ **GitHub** - Perfect for developer tools
* ๐ **Facebook** - Great for social apps
* ๐ฆ **Twitter** - Social media integration
* ๐ผ **LinkedIn** - B2B and professional apps
* ๐ **Apple** - iOS app requirement
* ๐ท **Microsoft** - Enterprise applications
* ๐ง **Email (Magic Links)** - Passwordless auth
## How to Add a Provider โก
The process is similar for all providers:
### 1. Get Provider Credentials ๐
Each provider requires:
* Client ID
* Client Secret
* Redirect URI configuration
### 2. Add Environment Variables ๐
```bash
# Example for GitHub
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
# Example for Facebook
FACEBOOK_CLIENT_ID=your_client_id
FACEBOOK_CLIENT_SECRET=your_client_secret
```
### 3. Configure in Auth.js ๐ ๏ธ
Providers are pre-configured in your `src/auth.ts` file. Just add your credentials to `.env` and they'll work automatically!
Most popular providers are already set up in Indie Kit. Just add the environment variables and you're good to go! ๐
## Provider Documentation ๐
For detailed setup guides for each provider:
* **Official Docs**: [Auth.js Providers](https://authjs.dev/getting-started/providers)
* **Provider List**: [All 80+ Providers](https://authjs.dev/reference/core/providers)
Each provider has specific setup instructions, callback URLs, and required scopes.
## Example: Adding GitHub Auth ๐
Quick example of adding GitHub:
1. **Create OAuth App** on GitHub
* Go to GitHub Settings > Developer Settings > OAuth Apps
* Click "New OAuth App"
* Set callback URL: `https://yourdomain.com/api/auth/callback/github`
2. **Add Credentials**
```bash
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
```
3. **Done!** Users can now sign in with GitHub โจ
Each provider has unique requirements. Check the [Auth.js documentation](https://authjs.dev/getting-started/providers) for specific setup instructions.
## Testing Your Provider ๐งช
After adding a provider:
1. Restart your dev server
2. Visit `/sign-in` page
3. You should see the new provider button
4. Test the authentication flow
## Next Steps ๐ฏ
* [User Authentication Guide](/setup/auth/user-authentication) - Use auth in your app
* [Password Login](/setup/auth/password-login) - Add traditional auth
# Cloudflare Email Setup (/setup/email/cloudflare)
# Setting Up Cloudflare Email Sending ๐ง
This guide will help you set up Cloudflare Email Sending for reliable email delivery in your application using their REST API.
## Prerequisites ๐
1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Your domain must be managed by Cloudflare DNS.
## Domain Verification ๐
Before using Email Sending, you need to configure your domain.
1. In the Cloudflare dashboard, go to **Email Sending** ([direct link](https://dash.cloudflare.com/?to=/:account/email-service/sending)).
2. Select **Onboard Domain**.
3. Choose a domain from your Cloudflare account.
4. Select **Continue** to proceed with DNS configuration.
5. Select **Add records and onboard**. This will automatically add the following DNS records on the `cf-bounce` subdomain of your domain:
* **MX records** for bounce handling.
* **TXT record** for SPF to authorize sending emails.
* **TXT record** for DKIM to provide authentication for emails sent from your domain.
* **TXT record** for DMARC on `_dmarc.yourdomain.com`.
DNS changes can take up to 24 hours to propagate globally, but usually complete within 5-15 minutes for domains using Cloudflare DNS. Once your domain is onboarded, you can start sending emails.
## Environment Variables ๐
Add these to your `.env` or `.env.local` file:
```bash
CLOUDFLARE_ACCOUNT_ID="your_account_id"
CLOUDFLARE_EMAIL_API_TOKEN="your_api_token"
```
*You can find your Account ID in the Cloudflare dashboard URL or on the overview page. Generate an API Token with Email Sending permissions.*
## Implementation ๐ป
Create or update `src/lib/email/sendMail.ts` to use the Cloudflare REST API:
```typescript
import { appConfig } from "../config";
const sendMail = async (to: string, subject: string, html: string) => {
if (process.env.NODE_ENV !== "production") {
console.log(
"Sending email to",
to,
"with subject",
subject,
"and html",
html
);
return;
}
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
const apiToken = process.env.CLOUDFLARE_EMAIL_API_TOKEN;
if (!accountId || !apiToken) {
console.error("Cloudflare email credentials are not set.");
return;
}
try {
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${accountId}/email/sending/send`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: to,
from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`,
subject: subject,
html: html,
// Optional: Add plain text version
// text: html.replace(/<[^>]*>?/gm, ''),
}),
}
);
const data = await response.json();
if (!response.ok || !data.success) {
console.error("Email sending failed:", data.errors || data);
} else {
if (process.env.NODE_ENV === "development") {
console.info("Email sent successfully", data);
}
}
} catch (error) {
console.error("Error sending email via Cloudflare:", error);
}
};
export default sendMail;
```
## Testing Email Setup ๐งช
1. Send a test email using a simple `curl` command to verify your token:
```bash
curl "https://api.cloudflare.com/client/v4/accounts//email/sending/send" \
--header "Authorization: Bearer " \
--header "Content-Type: application/json" \
--data '{
"to": "recipient@example.com",
"from": "welcome@yourdomain.com",
"subject": "Welcome to our service!",
"html": "
Welcome!
Thanks for signing up.
",
"text": "Welcome! Thanks for signing up."
}'
```
2. Trigger an email through your application flow (e.g., sign up or password reset).
3. Check the recipient inbox and verify the email headers for proper DKIM and SPF alignment.
## Important Notes โ ๏ธ
1. Ensure your `appConfig.email.senderEmail` matches the domain you onboarded in Cloudflare.
2. Keep an eye on your bounce rates and spam complaints to maintain a healthy sender reputation.
3. Cloudflare Email Sending is currently expanding its features, so check their dashboard for analytics and logs.
# Email Setup (/setup/email)
YouTube Video: https://www.youtube.com/watch?v=ZNeJHxaxG_4
Let's set up and customize your application's email system! ๐
## Email Authentication & Deliverability ๐ซ
Before setting up your email provider, understand these critical email authentication protocols:
### DKIM (DomainKeys Identified Mail) ๐
* Digital signature that proves email authenticity
* Prevents email spoofing and tampering
* Consists of:
1. Public key (published in DNS)
2. Private key (used to sign emails)
* Most providers auto-generate DKIM keys
* Example DNS record:
```txt
selector._domainkey.yourdomain.com TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4..."
```
### SPF (Sender Policy Framework) ๐ก๏ธ
* Specifies authorized servers to send emails
* Prevents unauthorized sending from your domain
* Single DNS TXT record listing allowed IPs/servers
* Example record:
```txt
yourdomain.com TXT "v=spf1 include:_spf.google.com include:_spf.mailgun.org ~all"
```
### DMARC (Domain-based Message Authentication) ๐
* Enforces SPF and DKIM policies
* Provides reporting on authentication failures
* Helps prevent domain spoofing
* Example record:
```txt
_dmarc.yourdomain.com TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"
```
* Policy options:
* `p=none`: Monitor only
* `p=quarantine`: Send to spam folder
* `p=reject`: Reject emails
### Additional Deliverability Tips ๐ก
1. **Warm Up Your Domain** ๐ก๏ธ
* Start with low volume
* Gradually increase sending
* Monitor engagement metrics
* Use consistent sending patterns
2. **List Hygiene** ๐งน
* Remove bounced emails
* Honor unsubscribe requests
* Validate email addresses
* Segment by engagement
3. **Technical Setup** โ๏ธ
* Set up reverse DNS (PTR records)
* Use dedicated IP (for high volume)
* Monitor IP/domain reputation
* Implement feedback loops
4. **Content Best Practices** ๐
* Balance text-to-image ratio
* Avoid spam trigger words
* Test with spam checkers
## Choose Your Email Provider ๐ฎ
Select your preferred email provider:
Set up Amazon Simple Email Service for production-ready email sending.
Modern email API with great deliverability and simple setup.
Send emails through Cloudflare's REST API with built-in DNS configuration.
Powerful email service with excellent analytics and tracking.
Popular email platform with HTML email support.
## Email Designer โจ
1. Start the email designer:
```bash
npm run dev
```
This will launch the email designer at `http://localhost:3001`
2. Use the designer to:
* Preview email templates
* Test responsive layouts
## Customizing Email Layout ๐จ
Customize your email branding in `src/emails/components/layout.tsx`:
```tsx
// src/emails/components/layout.tsx
// ...Existing code...
```
## Creating Email Templates ๐
Customize welcome email in `src/emails/components/Welcome.tsx`:
```tsx
export default function Welcome({
userName,
dashboardUrl
}: WelcomeEmailProps) {
return (
Welcome to {appConfig.projectName}! ๐Hi {userName},
We're excited to have you on board! Get started by visiting your dashboard:
If you have any questions, just reply to this email - we're here to help!
);
}
```
## Testing Emails ๐งช
1. **Local Testing**
* Use email designer at `localhost:3001`
* Preview all templates
* Test with different data
* Check responsive design
2. **Production Testing**
* Send test emails to real addresses
* Verify deliverability
* Check spam scores
* Monitor open rates
## Best Practices ๐ก
1. **Design**
* Keep emails responsive
* Use brand colors consistently
* Include plain text versions
* Optimize images
2. **Content**
* Clear subject lines
* Personalize content
* Include unsubscribe links
* Follow email regulations
3. **Technical**
* Set up SPF/DKIM
* Monitor bounce rates
* Handle email errors
* Track delivery status
Now your Indie Kit application is ready to send beautifully designed emails! ๐
# Mailchimp Setup (/setup/email/mailchimp)
# Setting Up Mailchimp ๐ง
This guide will help you set up Mailchimp for sending HTML emails in your application.
## Prerequisites ๐
1. A Mailchimp account
2. Access to your domain's DNS settings
3. Domain verified in Mailchimp
4. API key with send permissions
## Domain Verification ๐
1. Go to Mailchimp Dashboard
2. Navigate to Account > Domains
3. Add and verify your domain
4. Add the provided DNS records to your domain settings
## Implementation ๐ป
Install required dependencies:
```bash
pnpm add @mailchimp/mailchimp_transactional
```
Create `src/lib/email/sendMail.ts`:
```typescript
import mailchimp from "@mailchimp/mailchimp_transactional";
import { appConfig } from "../config";
const sendMail = async (to: string, subject: string, html: string) => {
if (process.env.NODE_ENV !== "production") {
console.log(
"Sending email to",
to,
"with subject",
subject,
"and html",
html
);
return;
}
const client = mailchimp(process.env.MAILCHIMP_API_KEY);
const message = {
html: html,
subject: subject,
from_email: appConfig.email.senderEmail,
from_name: appConfig.email.senderName,
to: [
{
email: to,
type: "to"
}
],
headers: {
"Reply-To": appConfig.email.senderEmail
}
};
const response = await client.messages.send({ message });
console.log("Email sent successfully", response);
};
export default sendMail;
```
## Environment Variables ๐
Add these to your `.env.local`:
```bash
MAILCHIMP_API_KEY=your_api_key
```
## Testing Email Setup ๐งช
1. Send a test email using Mailchimp dashboard
2. Monitor delivery in Mailchimp Activity Feed
3. Check email headers for proper authentication
## Important Notes โ ๏ธ
1. DNS propagation takes time (24-48 hours)
2. Monitor email reputation in Mailchimp dashboard
3. Keep bounce rate below recommended threshold
4. Use production API key in production
5. Only HTML emails are supported
Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐
# Mailgun Setup (/setup/email/mailgun)
# Setting Up Mailgun ๐ง
This guide will help you set up Mailgun for reliable email delivery in your application.
## Prerequisites ๐
1. A Mailgun account
2. Access to your domain's DNS settings
3. Domain verified in Mailgun
4. API key with sending permissions
## Domain Verification ๐
1. Go to Mailgun Dashboard
2. Navigate to Sending > Domains > Add New Domain
3. Follow the DNS verification steps
4. Add the provided DNS records (SPF, DKIM, DMARC) to your domain
## Implementation ๐ป
Install required dependencies:
```bash
pnpm add form-data mailgun.js
```
Create `src/lib/email/sendMail.ts`:
```typescript
import formData from "form-data";
import Mailgun from "mailgun.js";
import { appConfig } from "../config";
const sendMail = async (to: string, subject: string, html: string) => {
if (process.env.NODE_ENV !== "production") {
console.log(
"Sending email to",
to,
"with subject",
subject,
"and html",
html
);
return;
}
const mailgun = new Mailgun(formData);
const client = mailgun.client({
username: "api",
key: process.env.MAILGUN_API_KEY!,
});
const response = await client.messages.create(
process.env.MAILGUN_DOMAIN!,
{
from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`,
to: [to],
subject: subject,
html: html,
"h:Reply-To": appConfig.email.senderEmail,
}
);
console.log("Email sent successfully", response);
};
export default sendMail;
```
## Environment Variables ๐
Add these to your `.env.local`:
```bash
MAILGUN_API_KEY=your_api_key
MAILGUN_DOMAIN=your_verified_domain
```
## Testing Email Setup ๐งช
1. Send a test email using Mailgun dashboard
2. Monitor delivery in Mailgun logs
3. Check email headers for proper authentication
4. Use the Mailgun Email Test feature
## Important Notes โ ๏ธ
1. DNS propagation takes time (24-48 hours)
2. Start with sandbox domain for testing
3. Monitor email reputation in Mailgun dashboard
4. Keep bounce rate below 5%
5. Use production API key in production
Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐
# Resend Setup (/setup/email/resend)
# Setting Up Resend ๐ง
This guide will help you set up Resend for reliable email delivery in your application.
## Prerequisites ๐
1. A Resend account (sign up at [resend.com](https://resend.com))
2. Access to your domain's DNS settings
3. Domain verified in Resend
## Domain Verification ๐
1. Go to Resend Dashboard
2. Navigate to Domains > Add Domain
3. Follow the DNS verification steps
4. Add the provided DNS records to your domain
## Implementation ๐ป
Install required dependencies:
```bash
pnpm add resend
```
Create `src/lib/email/sendMail.ts`:
```typescript
import { Resend } from "resend";
import { appConfig } from "../config";
const sendMail = async (to: string, subject: string, html: string) => {
if (process.env.NODE_ENV !== "production") {
console.log(
"Sending email to",
to,
"with subject",
subject,
"and html",
html
);
return;
}
const resend = new Resend(process.env.RESEND_API_KEY);
const response = await resend.emails.send({
from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`,
to: [to],
subject: subject,
html: html,
replyTo: appConfig.email.senderEmail,
});
if(response.error) {
console.error("Email sent failed", response.error);
} else {
if(process.env.NODE_ENV === "development") {
console.info("Email sent successfully", response);
}
}
};
export default sendMail;
```
## Environment Variables ๐
Add these to your `.env.local`:
```bash
RESEND_API_KEY=your_api_key
```
## Testing Email Setup ๐งช
1. Send a test email using Resend dashboard
2. Monitor delivery in Resend logs
3. Check email headers for proper authentication
## Important Notes โ ๏ธ
1. DNS propagation takes time (24-48 hours)
2. Monitor email reputation in Resend dashboard
3. Keep bounce rate low
4. Use production API key in production
Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐
# Amazon SES Setup (/setup/email/ses)
# Setting Up Amazon SES ๐ง
This guide will help you set up Amazon Simple Email Service (SES) with proper DNS records to ensure reliable email delivery and prevent your emails from being marked as spam.
## Prerequisites ๐
1. An AWS account
2. Access to your domain's DNS settings
3. Domain verified in AWS SES
4. A production SES account (out of sandbox)
## Domain Verification ๐
1. Go to AWS SES Console
2. Click "Verified Identities" > "Create Identity"
3. Choose "Domain" and enter your domain name
4. **Important**: Use AWS-provided DKIM, DMARC, and SPF records
5. Copy the DNS records from AWS dashboard
6. Add them to your domain's DNS settings
## Requesting Production Access ๐
When applying for production access, use this template:
```txt
Use Case Description:
I am developing a SaaS platform that requires email functionality for essential user communications. Our email sending will be strictly limited to registered and verified users only. Key email use cases include:
1. User Authentication & Security
- Account verification emails
- Password reset notifications
- Security alerts
2. User Engagement
- Waitlist notifications
- Welcome emails
- Product updates
- Service notifications
3. Email Management
- We have implemented proper bounce and complaint handling
- All recipients are verified users who have explicitly opted in
- Unsubscribe links are included in all marketing communications
- We follow email best practices and maintain clean recipient lists
4. Security Measures
- DMARC, SPF, and DKIM are properly configured
- Regular monitoring of bounce rates and complaints
- Immediate removal of hard bounces
- Compliance with anti-spam regulations
```
## Implementation ๐ป
Install required dependencies:
```bash
pnpm add @aws-sdk/client-ses
```
Create `src/lib/email/sendMail.ts`:
```typescript
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
import { appConfig } from "../config";
const sendMail = async (to: string, subject: string, html: string) => {
if (process.env.NODE_ENV !== "production") {
console.log(
"Sending email to",
to,
"with subject",
subject,
"and html",
html
);
return;
}
const ses = new SESClient({
region: process.env.AWS_SES_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
const command = new SendEmailCommand({
Destination: {
ToAddresses: [to],
},
Message: {
Body: {
Html: {
Data: html,
},
},
Subject: {
Data: subject,
},
},
Source: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`,
ReplyToAddresses: [appConfig.email.senderEmail],
});
const response = await ses.send(command);
console.log("Email sent successfully", response);
};
export default sendMail;
```
## Environment Variables ๐
Add these to your `.env.local`:
```bash
AWS_SES_REGION=us-east-1
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
```
## Testing Email Setup ๐งช
1. Send a test email using AWS SES console
2. Check email headers for proper DKIM alignment
3. Use AWS SES dashboard to track bounces and complaints
## Important Notes โ ๏ธ
1. DNS propagation takes time (24-48 hours)
2. Start in SES sandbox for testing
3. Request production access before sending bulk emails
4. Keep bounce rate below 5%
5. Monitor email reputation regularly
Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐
# Dodo Payments Integration (/setup/payments/dodo)
# Dodo Payments Integration ๐ณ
Let's set up Dodo Payments for your Indie Kit application! ๐
## Initial Setup โก
1. Create a [Dodo Payments account](https://app.dodopayments.com/signup)
2. Set up your business details
3. Add required legal documents:
* Privacy Policy
* Terms of Service
4. Add these environment variables to your `.env` file:
```bash
# Dodo Payments Configuration
DODO_PAYMENTS_API_KEY=xxx... # From Dodo Payments API keys
DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxx... # From Dodo Payments webhook settings
DODO_PAYMENTS_API_URL="https://live.dodopayments.com" # Or test.dodopayments.com for testing
```
Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables.
Make sure to use the correct API URL for your environment.
## Product Configuration ๐๏ธ
In your Dodo Payments Dashboard:
1. Create Products with pricing:
* Monthly subscriptions
* Yearly subscriptions
* One-time payments
2. Customer Portal:
* Already enabled by default
## Webhook Setup ๐
1. Add your webhook endpoint in Dodo Payments Dashboard:
```
https://your-domain.com/api/webhooks/dodo
```
2. Enable these webhook events:
* payment.succeeded
* payment.failed
* payment.processing
* payment.cancelled
* refund.succeeded
* refund.failed
* dispute.opened
* dispute.expired
* dispute.accepted
* dispute.cancelled
* dispute.challenged
* dispute.won
* dispute.lost
* subscription.created
* subscription.active
* subscription.on\_hold
* subscription.renewed
* subscription.paused
* subscription.cancelled
* subscription.failed
* license\_key.created
* customer.created
All these events are required for proper subscription management. They handle:
* Customer creation and management
* Subscription lifecycle (creation, updates, deletion)
* Invoice payments
3. For local testing, use `ngrok`:
```bash
# Forward webhooks to your local server
ngrok http 3000
```
## Plan Mapping ๐บ๏ธ
1. Go to your super admin dashboard: `/super-admin/plans`
2. For each plan, add the corresponding Dodo Payments Price IDs:
* `monthlyDodoPriceId`
* `yearlyDodoPriceId`
* `onetimeDodoPriceId`
Find Price IDs in Dodo Payments Dashboard under Products โ Select Product
## Adding Subscribe Buttons ๐
Use the `getSubscribeUrl` helper to create subscription links:
```tsx
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
function PricingCard({ plan }) {
// Monthly subscription with 7-day trial
const monthlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.MONTHLY,
provider: PlanProvider.DODO,
trialPeriodDays: 7
})
// Yearly subscription with 14-day trial
const yearlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.YEARLY,
provider: PlanProvider.DODO,
trialPeriodDays: 14
})
// One-time payment
const onetimeUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.ONETIME,
provider: PlanProvider.DODO
})
return (
)
}
```
## Features Available ๐ฏ
* ๐ Automatic plan upgrades/downgrades
* ๐ณ Dodo Payments-managed billing
* ๐ช Customer portal for subscription management
* โก Webhook handling
* ๐ Payment tracking
* ๐ Usage monitoring
## Best Practices ๐ก
1. **Testing**
* Use `ngrok` for local webhook testing
* Test all subscription flows
* Verify upgrade/downgrade paths
2. **Production**
* Add all required legal documents
* Configure proper webhook security
* Monitor webhook events
* Set up alerts for failed payments
3. **Customer Experience**
* Clear pricing information
* Smooth upgrade flow
* Easy access to billing portal
* Clear trial period information
Now your Indie Kit application is ready to accept payments and manage subscriptions through Dodo Payments! ๐
# Payments (/setup/payments)
YouTube Video: https://www.youtube.com/watch?v=9keJnstTID0
Set up flexible pricing plans to monetize your SaaS! Support for multiple payment providers and pricing models. ๐ฐ
Indie Kit supports Stripe, LemonSqueezy, PayPal, DodoPayments, and Polar.sh. Choose the one that works best for your region and needs!
## Managing Plans โก
Access the super admin dashboard at `/super-admin/plans` to create and manage your plans.
You need super admin access to manage plans. Set `SUPER_ADMIN_EMAILS` in your `.env` file.
### Plan Structure ๐
Each plan includes:
```typescript
{
name: "Pro Plan", // Display name
codename: "pro", // Unique identifier (used in URLs)
default: false, // Auto-assigned to new users?
// Pricing Options
hasMonthlyPricing: true, // Enable monthly subscription
hasYearlyPricing: true, // Enable yearly subscription
hasOnetimePricing: false, // Enable lifetime/one-time payment
// Prices (in cents)
monthlyPrice: 2900, // $29/month
yearlyPrice: 29000, // $290/year (save $58!)
onetimePrice: 50000, // $500 lifetime
// Features
featuresList: [
"Unlimited projects",
"Priority support",
"Advanced analytics"
],
quotas: {
canUseApp: true,
projects: 100,
apiCalls: 10000
}
}
```
**Key fields:**
* ๐ฐ **Prices in cents** - $29.00 = 2900 cents
* ๐ท๏ธ **Codename** - Used in URLs and code (lowercase, no spaces)
* โจ **Features list** - Shows on pricing page
* ๐ **Quotas** - Limits enforced in your app
### Default Plan ๐ฏ
One plan should be marked as default:
```typescript
{
default: true, // This plan for new signups
}
```
**Default plan behavior:**
* โ Automatically assigned to new users
* โ No expiration date
* โ Users keep it until they upgrade
* โ Should be your free/basic tier
Set your free tier as the default plan to let users try your product before upgrading! Convert them later with upgrade prompts.
## Configuring Quotas โ๏ธ
Quotas control what users can do based on their plan. Define them in `src/db/schema/plans.ts`:
```typescript
export const quotaSchema = z.object({
canUseApp: z.boolean().default(true),
projects: z.number(),
apiCalls: z.number(),
storage: z.string(),
})
```
**Quota types:**
* ๐ฆ **Boolean flags** - Feature on/off (e.g., `canExport: true`)
* ๐ข **Numerical limits** - Usage caps (e.g., `projects: 10`)
* ๐ **String values** - Tier levels (e.g., `support: "priority"`)
### Example Quotas ๐
```typescript
// Free Plan
{
canUseApp: true,
projects: 3,
apiCalls: 100,
storage: "1GB"
}
// Pro Plan
{
canUseApp: true,
projects: 100,
apiCalls: 10000,
storage: "100GB"
}
// Enterprise Plan
{
canUseApp: true,
projects: -1, // -1 = unlimited
apiCalls: -1,
storage: "unlimited"
}
```
Check quotas in your app logic to gate features. Example: `if (user.plan.quotas.projects >= maxProjects) { showUpgradePrompt() }`
## Pricing Models ๐ณ
Indie Kit supports three pricing models:
### 1. Monthly Subscriptions
```typescript
{
hasMonthlyPricing: true,
monthlyPrice: 2900, // $29/month
monthlyStripePriceId: "price_xxx",
}
```
**Best for:** SaaS products with ongoing value
### 2. Yearly Subscriptions
```typescript
{
hasYearlyPricing: true,
yearlyPrice: 29000, // $290/year
yearlyStripePriceId: "price_yyy",
}
```
**Best for:** Offering discounts for annual commitment
### 3. One-Time Payments
```typescript
{
hasOnetimePricing: true,
onetimePrice: 50000, // $500 lifetime
onetimeStripePriceId: "price_zzz",
}
```
**Best for:** Lifetime deals, LTD campaigns, course access
Enable multiple pricing models per plan! Let users choose between monthly, yearly, or lifetime access.
## Payment Provider Setup ๐ง
Choose and configure your payment provider:
**Popular options:**
* ๐ฆ [Stripe](/setup/payments/stripe) - Global, feature-rich (recommended)
* ๐ณ [Dodo Payments](/setup/payments/dodo) - Merchant of record, handles tax
* ๐ [PayPal](/setup/payments/paypal) - Trusted, worldwide
* ๐ป [Polar.sh](/setup/payments/polar) - Great for developers and open source
Each provider guide includes API key setup and webhook configuration.
## Next Steps ๐
1. **Create your plans** in `/super-admin/plans`
2. **Connect payment provider** - Pick from guides above
3. **Test checkout flow** - Use provider test cards
4. **Display pricing** on your site using [plan-based rendering](/payments/current-plan-based-rendering)
Your payment system is ready! Start creating plans and connecting your payment provider. ๐ฐ
## Related Documentation ๐
* [Managing Plans](/payments/managing-plans) - Create, edit, and organize plans
* [Create Subscription](/payments/create-subscription) - Implement checkout flow
* [Credits System](/payments/credits-system) - Usage-based pricing
* [Running LTD Campaigns](/payments/running-ltd-campaigns) - Lifetime deal strategies
# LemonSqueezy Integration (/setup/payments/lemonsqueezy)
# LemonSqueezy Integration ๐
Let's set up LemonSqueezy payments for your Indie Kit application! ๐
## Initial Setup โก
1. Create a [LemonSqueezy account](https://app.lemonsqueezy.com/register)
2. Set up your store details
3. Add these environment variables to your `.env` file:
```bash
# LemonSqueezy Configuration
LEMON_SQUEEZY_API_KEY=your-api-key
LEMON_SQUEEZY_STORE_ID=your-store-id
LEMON_SQUEEZY_WEBHOOK_SECRET=your-webhook-secret
```
Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables.
## Product Configuration ๐๏ธ
In your LemonSqueezy Dashboard:
1. Create Products with variants:
* Monthly subscriptions
* Yearly subscriptions
* One-time payments
2. For each product:
* Set up pricing
* Configure billing cycles
* Add product descriptions
* Set up trial periods (if needed)
## Webhook Setup ๐
1. Add your webhook endpoint in LemonSqueezy Dashboard:
```
https://your-domain.com/api/webhooks/lemonsqueezy
```
2. Enable these webhook events:
* `subscription_created`
* `subscription_updated`
* `subscription_cancelled`
* `subscription_resumed`
* `order_created`
## Plan Mapping ๐บ๏ธ
1. Go to your super admin dashboard: `/super-admin/plans`
2. For each plan, add the corresponding LemonSqueezy Product IDs:
* `monthlyLemonSqueezyProductId`
* `yearlyLemonSqueezyProductId`
* `onetimeLemonSqueezyProductId`
Find Product IDs in LemonSqueezy Dashboard under Products โ Select Product โ Variants
## Adding Subscribe Buttons ๐
Use the `getSubscribeUrl` helper to create subscription links:
```tsx
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
function PricingCard({ plan }) {
// Monthly subscription with 7-day trial
const monthlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.MONTHLY,
provider: PlanProvider.LEMON_SQUEEZY,
trialPeriodDays: 7
})
// Yearly subscription with 14-day trial
const yearlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.YEARLY,
provider: PlanProvider.LEMON_SQUEEZY,
trialPeriodDays: 14
})
// One-time payment
const onetimeUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.ONETIME,
provider: PlanProvider.LEMON_SQUEEZY
})
return (
)
}
```
## Features Available ๐ฏ
* ๐ Automatic plan upgrades/downgrades
* ๐ณ LemonSqueezy-managed billing
* ๐ช Customer portal access
* โก Webhook handling
* ๐ Payment tracking
* ๐ Usage monitoring
## Best Practices ๐ก
1. **Testing**
* Use test mode in LemonSqueezy
* Test all subscription flows
* Verify webhook events
* Test upgrade/downgrade paths
2. **Production**
* Configure proper webhook security
* Monitor webhook events
* Set up email notifications
* Keep product IDs in sync
3. **Customer Experience**
* Clear pricing information
* Smooth checkout flow
* Easy access to billing portal
* Clear trial period information
## Advantages of LemonSqueezy ๐
* Simple setup process
* Built-in EU VAT handling
* Modern checkout experience
* Lower transaction fees
* Excellent developer experience
* Quick payouts
Now your Indie Kit application is ready to accept payments and manage subscriptions through LemonSqueezy! ๐
# Paddle Payments Integration (/setup/payments/paddle)
# Paddle Payments Integration ๐ณ
Let's set up Paddle Payments for your Indie Kit application! ๐
## Initial Setup โก
1. Create a [Paddle test account](https://sandbox-vendors.paddle.com/)
2. Set up your business details
3. **Important First Step**: Request domain approval:
* Go to [Request Domain Approval](https://sandbox-vendors.paddle.com/request-domain-approval)
* Sandbox approval is immediate, but production can take time
* **Recommended**: Do this first as you build your application
Production domain approval can take time. Request it early in your development process!
4. Add these environment variables to your `.env` file:
```bash
# Paddle Payments Configuration
PADDLE_API_KEY=pdl_sdbx_apikey_xxx... # From Developer Tools > Authentication > API Keys
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_xxx... # From Developer Tools > Authentication > Client Side Tokens
PADDLE_WEBHOOK_SECRET=pdl_ntfset_xxx... # From Developer Tools > Notifications > Webhook Settings
PADDLE_CREDITS_PRODUCT_ID=pro_xxx... # Your credits product ID
```
Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables.
## Getting Your Credentials ๐
### API Key
1. Go to Dashboard โ **Developer Tools** โ **Authentication** โ **API Keys**
2. Copy your API key
### Client Token
1. Go to Dashboard โ **Developer Tools** โ **Authentication** โ **Client Side Tokens**
2. Copy your client token (use test token for development)
### Webhook Secret
1. Go to Dashboard โ **Developer Tools** โ **Notifications**
2. Click **Add Webhook** or **Edit Webhook**
3. Copy the webhook secret
## Domain & Checkout Configuration ๐
1. **Set up your domain** in Paddle Dashboard (if not done during approval)
2. **Configure Default Payment Link**:
* Go to [Checkout Settings](https://sandbox-vendors.paddle.com/checkout-settings)
* Set **Default payment link**:
* For localhost: `http://localhost:3000/app/subscribe/paddle`
* For production: `https://your-domain.com/app/subscribe/paddle`
Make sure your domain is already set up in the Paddle Dashboard before configuring the payment link.
## Product Configuration ๐๏ธ
In your Paddle Dashboard:
1. Create Products with pricing:
* Monthly subscriptions
* Yearly subscriptions
* One-time payments
2. **Credits Product Setup** (if using credits system):
* Go to Dashboard โ **Catalogue** โ **Products**
* Click **Create Product**
* Name it **"Credits"** for better understanding
* **No need to create prices** - Indie Kit will create them automatically
* Copy the Product ID and add it to your `.env` as `PADDLE_CREDITS_PRODUCT_ID`
Indie Kit automatically creates prices for the Credits product. You only need to create the product itself in Paddle Dashboard.
3. Customer Portal:
* Already enabled by default
## Webhook Setup ๐
1. Go to Dashboard โ **Developer Tools** โ **Notifications**
2. Click **Add Webhook** or **Edit Webhook**
3. Add your webhook endpoint:
```
https://your-domain.com/api/webhooks/paddle
```
4. Copy the webhook secret and add it to your `.env` file
5. For local testing, use `ngrok`:
```bash
# Forward webhooks to your local server
ngrok http 3000
```
Then use the ngrok URL in your webhook configuration:
```
https://your-ngrok-url.ngrok.io/api/webhooks/paddle
```
Use ngrok to test webhooks locally. Update the webhook URL in Paddle Dashboard with your ngrok URL.
## Plan Mapping ๐บ๏ธ
1. Go to your super admin dashboard: `/super-admin/plans`
2. For each plan, add the corresponding Paddle Price IDs:
* `monthlyPaddlePriceId`
* `yearlyPaddlePriceId`
* `onetimePaddlePriceId`
Find Price IDs in Paddle Dashboard under Products โ Select Product โ Pricing
## Adding Subscribe Buttons ๐
Use the `getSubscribeUrl` helper to create subscription links:
```tsx
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
function PricingCard({ plan }) {
// Monthly subscription with 7-day trial
const monthlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.MONTHLY,
provider: PlanProvider.PADDLE,
trialPeriodDays: 7
})
// Yearly subscription with 14-day trial
const yearlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.YEARLY,
provider: PlanProvider.PADDLE,
trialPeriodDays: 14
})
// One-time payment
const onetimeUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.ONETIME,
provider: PlanProvider.PADDLE
})
return (
)
}
```
## Features Available ๐ฏ
* ๐ Automatic plan upgrades/downgrades
* ๐ณ Paddle-managed billing
* ๐ช Customer portal for subscription management
* โก Webhook handling
* ๐ Payment tracking
* ๐ Usage monitoring
* ๐ฐ Credits system support
## Best Practices ๐ก
1. **Testing**
* Use sandbox environment for testing
* Use `ngrok` for local webhook testing
* Test all subscription flows
* Verify upgrade/downgrade paths
* Test trial periods
2. **Production**
* Request domain approval early (can take time)
* Configure proper webhook security
* Monitor webhook events
* Set up alerts for failed payments
* Keep Price IDs in sync with your plans
3. **Customer Experience**
* Clear pricing information
* Smooth checkout flow
* Easy access to billing portal
* Clear trial period information
* Responsive checkout experience
Before going live:
* โ Domain approved in production
* โ Production API keys configured
* โ Webhook endpoint secured
* โ Default payment link set correctly
* โ All Price IDs mapped in super admin
Now your Indie Kit application is ready to accept payments and manage subscriptions through Paddle Payments! ๐
# PayPal Integration (/setup/payments/paypal)
import { Tabs, Tab } from 'fumadocs-ui/components/tabs'
# Setting Up PayPal Payments ๐ฐ
Indie Kit supports PayPal for both one-time purchases and recurring subscriptions. Follow this guide to get started! ๐
## Initial PayPal Setup ๐ ๏ธ
Before configuring payment types, you'll need to set up your PayPal developer environment:
### 1. Create a PayPal Developer Account ๐ค
* Go to [developer.paypal.com](https://developer.paypal.com)
* Click "Log in to Dashboard" and create your account
* Complete the developer verification process
### 2. Create Test Accounts ๐งช
* In your PayPal Developer Dashboard, go to "Sandbox" > "Accounts"
* Create **one Test Business Account** (this will be your merchant account)
* Create **one Test Personal Account** (for testing purchases)
* Note down the credentials for both accounts
### 3. Get API Credentials ๐
* Go to "My Apps & Credentials" in your developer dashboard
* Click "Create App" under the "Sandbox" section
* Choose your business account and give your app a name
* Copy your **Client ID** and **Secret Key**
### 4. Set Up Environment Variables ๐
Add these to your `.env.local` file:
```bash
# PayPal Sandbox Configuration
NEXT_PUBLIC_PAYPAL_CLIENT_ID='AQLowgpUG45c3c6WpgPvkaCAqYh36dvtwjzeuMoJZnQydqnXgRHgF8HE3-jD26aCW8-9_2lmsWm5Dfzo'
PAYPAL_SECRET_KEY='EP01knzIEI8C8jGGOvRsVj41uX_AYouvCgnrB1JpFhSiUk6szFDYSrUXKkT4xdzSxKs33qg-00q1NqJw'
NEXT_PUBLIC_PAYPAL_IS_SANDBOX=true # This is for test payments, set to false for production
```
### 5. Configure Webhooks ๐ช
For local development, you'll need ngrok:
```bash
# Install ngrok (if not already installed)
npm install -g ngrok
# Start your Next.js app
npm run dev
# In another terminal, expose your local server
ngrok http 3000
```
Then in PayPal Developer Dashboard:
* Go to "Webhooks" section
* Click "Create Webhook"
* Use your ngrok URL: `https://your-ngrok-url.ngrok.io/api/webhooks/paypal`
* Select relevant events (payments, subscriptions)
* Copy the **Webhook ID** and add to your environment:
```bash
PAYPAL_WEBHOOK_ID=1VC691815S403080G
```
Testing locally with ngrok is highly recommended before deploying to production. This ensures your webhook handling works correctly!
## Setting up One-time Payments ๐ธ
Perfect for products, courses, or any single purchase!
### 6. Create Plan in Database ๐
* Go to `/super-admin/plans/` in your app
* Create a new plan and add setup the onetime price.
### 7. Using the Subscribe Function ๐
Your existing `getSubscribeUrl` function already supports PayPal:
```tsx
// Usage for one-time payment
const subscribeUrl = getSubscribeUrl({
codename: "pro", // From database
type: PlanType.ONETIME,
provider: PlanProvider.PAYPAL,
});
// Use in components/pages
Checkout with PayPal
```
### 8. Test Your Setup โ
* Use your test personal account to make a purchase
* Verify the webhook receives payment confirmations
* Check that the payment status updates correctly
- Digital products
- Course purchases
- One-time software licenses
- Ebooks and resources
## Setting up Recurring Payments ๐
Great for SaaS subscriptions and membership sites!
### 6. Create Subscription Plan in PayPal ๐
* Log into your **Sandbox Business Account**
* Go to "Business Tools" > "Subscriptions" > "Get Started"
* Click "Create Plan"
* You might need to create a **Product** first, then the plan
* Configure your plan:
* **Billing cycle**: Monthly or Yearly
* **Setup fee**: Optional initial charge
* **Trial period**: If desired
* **Copy the Plan ID** after creation
### 7. Add Plan ID to Database ๐๏ธ
* Go to `/super-admin/plans/` in your app
* Create or edit your plan and add the paypal plan id in monthly or yearly price section. No need to specify the price in cents here as it's managed by paypal.
### 8. Using the Subscribe Function ๐
```tsx
// Usage for monthly subscription
const monthlyUrl = getSubscribeUrl({
codename: "pro", // From database
type: PlanType.MONTHLY,
provider: PlanProvider.PAYPAL,
});
// Usage for yearly subscription
const yearlyUrl = getSubscribeUrl({
codename: "pro", // From database
type: PlanType.YEARLY,
provider: PlanProvider.PAYPAL,
});
// Use in components/pages
Subscribe monthly with PayPal
Subscribe yearly with PayPal
```
### 9. Test Subscription Flow ๐งช
* Subscribe using your test personal account
* Verify subscription creation and billing
* Test subscription cancellation
* Check webhook notifications for all events
- SaaS applications
- Membership sites
- Monthly/yearly services
- Content subscriptions
## Production Configuration ๐
When you're ready to go live:
### Update Environment Variables ๐
Replace your sandbox credentials with production ones:
```bash
# Production PayPal Configuration
NEXT_PUBLIC_PAYPAL_CLIENT_ID='your-production-client-id'
PAYPAL_SECRET_KEY='your-production-secret-key'
NEXT_PUBLIC_PAYPAL_IS_SANDBOX=false
PAYPAL_WEBHOOK_ID='your-production-webhook-id'
```
### Production Webhook Setup ๐
* Create a new webhook in PayPal's production dashboard
* Use your actual domain: `https://yourdomain.com/api/webhooks/paypal`
* Update the webhook ID in your environment variables
## Troubleshooting ๐
Common issues and solutions:
### 1. Webhook Not Receiving Events ๐ก
* Verify your ngrok tunnel is running for local development
* Check that webhook URL is correctly set in PayPal dashboard
* Ensure your webhook endpoint is properly implemented
* Look at PayPal webhook logs for delivery attempts
### 2. Payment Authentication Errors ๐
* Double-check your Client ID and Secret Key
* Verify you're using the correct sandbox/production environment
* Ensure your PayPal app has the necessary permissions
### 3. Subscription Creation Fails ๐
* Verify the PayPal Plan ID is correct
* Check that the plan is active in your PayPal dashboard
* Ensure your business account has subscription features enabled
### 4. Environment Variable Issues โ๏ธ
* Restart your development server after changing `.env.local`
* Verify variable names match exactly (case-sensitive)
* Check for typos in your PayPal credentials
## Next Steps ๐ฏ
Now that PayPal is configured:
* Test thoroughly with sandbox accounts before going live
* Set up proper error handling for failed payments
* Implement subscription management features
* Configure email notifications for payment events
* Set up analytics to track payment conversions
Your PayPal integration is ready! Start accepting payments with confidence! ๐ช
Always test your payment flows thoroughly in sandbox mode before switching to production. PayPal's sandbox environment closely mimics production behavior.
# Polar.sh Integration (/setup/payments/polar)
# Polar.sh Integration ๐ป
Let's set up Polar.sh payments for your Indie Kit application! ๐
## Initial Setup โก
1. Create a [Polar.sh sandbox account](https://sandbox.polar.sh/dashboard/) for development.
2. Add these environment variables to your `.env` file:
```bash
# Polar (https://polar.sh/docs/features/checkout/session) โ use sandbox for dev
POLAR_ACCESS_TOKEN=""
POLAR_WEBHOOK_SECRET=""
# Server environment: production | sandbox
POLAR_SERVER="sandbox"
# One catalog product used as the line item for dynamic credit purchases (ad-hoc price set in checkout)
POLAR_CREDITS_PRODUCT_ID=""
```
Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables.
## Product Configuration ๐๏ธ
In your Polar.sh Dashboard:
1. Create Products with pricing:
* Monthly subscriptions
* Yearly subscriptions
* One-time payments
2. Go to your super admin dashboard (`/super-admin/plans`) and add the corresponding Polar Product IDs to your plans.
3. **For Credits System:** Create a "Pay what you want" product and add its ID to the `POLAR_CREDITS_PRODUCT_ID` environment variable.
## Webhook Setup ๐
For local testing, you can use the Polar CLI to forward webhooks to your local server.
1. Install the Polar CLI:
```bash
curl -fsSL https://polar.sh/install.sh | bash
```
2. Start a tunnel to relay webhooks automatically to your specified URL:
```bash
polar listen http://localhost:3000/api/webhooks/polar
```
You will see output similar to this:
```bash
โ Select Organization โฆ My Organization
Connected My Organization
Secret 6t3c8ce2247c493a3ade20uea4484d64
Forwarding http://localhost:3000/api/webhooks/polar
Waiting for events...
```
3. Copy the `Secret` from the output and set it as your `POLAR_WEBHOOK_SECRET` in your `.env` file.
For production, you'll need to add your webhook endpoint (`https://your-domain.com/api/webhooks/polar`) directly in the Polar.sh Dashboard and use the production webhook secret.
## Plan Mapping ๐บ๏ธ
1. Go to your super admin dashboard: `/super-admin/plans`
2. For each plan, add the corresponding Polar Product IDs:
* `monthlyPolarPriceId` (or equivalent field)
* `yearlyPolarPriceId`
* `onetimePolarPriceId`
## Best Practices ๐ก
1. **Testing**
* Always use the `sandbox` environment (`POLAR_SERVER="sandbox"`) during development.
* Use the Polar CLI for local webhook testing.
* Test all subscription flows and credit purchases.
2. **Production**
* Switch `POLAR_SERVER` to `"production"`.
* Update your access token and webhook secret with production credentials.
* Configure proper webhook security.
Now your Indie Kit application is ready to accept payments and manage subscriptions through Polar.sh! ๐
# Stripe Integration (/setup/payments/stripe)
# Stripe Integration ๐ณ
Let's set up Stripe payments for your Indie Kit application! ๐
## Initial Setup โก
1. Create a [Stripe account](https://dashboard.stripe.com/register)
2. Set up your business details
3. Add required legal documents:
* Privacy Policy
* Terms of Service
4. Add these environment variables to your `.env` file:
```bash
# Stripe Configuration
STRIPE_WEBHOOK_SECRET=whsec_xxx... # From Stripe webhook settings
STRIPE_PUBLISHABLE_KEY=pk_test_xxx... # From Stripe API keys
STRIPE_SECRET_KEY=sk_test_xxx... # From Stripe API keys
```
Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables.
Keys starting with `pk_test_` and `sk_test_` are for testing. Use `pk_live_` and `sk_live_` in production.
## Product Configuration ๐๏ธ
In your Stripe Dashboard:
1. Create Products with pricing:
* Monthly subscriptions
* Yearly subscriptions
* One-time payments
2. Enable Customer Portal:
* Go to Settings โ Customer Portal
* Add products for upgrade/downgrade flows
* Configure branding
## Webhook Setup ๐
1. Add your webhook endpoint in Stripe Dashboard:
```
https://your-domain.com/api/webhooks/stripe
```
2. Enable these webhook events:
* `invoice.paid`
* `customer.created`
* `customer.subscription.created`
* `customer.subscription.updated`
* `customer.subscription.deleted`
All these events are required for proper subscription management. They handle:
* Customer creation and management
* Subscription lifecycle (creation, updates, deletion)
* Invoice payments
3. For local testing, use Stripe CLI:
```bash
# Login to Stripe
stripe login
# Forward webhooks to your local server
stripe listen --forward-to http://localhost:3000/api/webhooks/stripe
```
## Plan Mapping ๐บ๏ธ
1. Go to your super admin dashboard: `/super-admin/plans`
2. For each plan, add the corresponding Stripe Price IDs:
* `monthlyStripePriceId`
* `yearlyStripePriceId`
* `onetimeStripePriceId`
Find Price IDs in Stripe Dashboard under Products โ Select Product โ Pricing
## Adding Subscribe Buttons ๐
Use the `getSubscribeUrl` helper to create subscription links:
```tsx
import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl'
function PricingCard({ plan }) {
// Monthly subscription with 7-day trial
const monthlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.MONTHLY,
provider: PlanProvider.STRIPE,
trialPeriodDays: 7
})
// Yearly subscription with 14-day trial
const yearlyUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.YEARLY,
provider: PlanProvider.STRIPE,
trialPeriodDays: 14
})
// One-time payment
const onetimeUrl = getSubscribeUrl({
codename: plan.codename,
type: PlanType.ONETIME,
provider: PlanProvider.STRIPE
})
return (