A self-hostable, open-source course platform with Stripe one-time payments, gated MDX lessons, and per-lesson progress tracking. Keep ~97% of revenue instead of handing half to a marketplace.
- Course catalog with free intro lessons as the funnel
- One-time Stripe Checkout granting lifetime access (idempotent webhook)
- Gated MDX lessons — free lessons public, the rest paywalled
- Per-lesson progress tracking and a "My learning" dashboard
- GitHub auth via NextAuth + Prisma
- Account/data deletion endpoint (privacy compliance)
- Centralized, validated env config in
lib/env.ts
- Add an entry to
lib/courses.tswith its lessons,amount, and a stablelookupKey. - Create
content/courses/<courseId>/<lessonSlug>.mdxfor each lesson. - Run
pnpm stripe:syncto provision the one-time Price in Stripe.
pnpm install # runs `prisma generate`
cp .env.example .env.local # fill in DATABASE_URL, auth, Stripe
pnpm db:push # create the schema
pnpm devSet a Stripe webhook to /api/stripe/webhook and configure STRIPE_WEBHOOK_SECRET. All variables are documented in .env.example.
Courses define their price in code (lib/courses.ts) via amount + a stable lookupKey. Provision them with:
pnpm stripe:syncThis idempotently creates/updates the Products and one-time Prices in whatever account STRIPE_SECRET_KEY belongs to (run it with your key). At runtime the app resolves the live price ID by lookupKey via lib/stripe-prices.ts — no price IDs in env or code.
One-time course sales ($99–$299) with bundle potential. Because it is self-hosted, you keep almost everything after Stripe fees.
pnpm dev/pnpm build/pnpm startpnpm lint/pnpm typecheckpnpm db:pushpnpm stripe:sync— provision Stripe products/prices fromlib/courses.ts
MIT — see LICENSE.