Coachly documentation
Run it, point it at your own accounts, ship it under your own brand. Written against version 1.1.0 of the download you get on CodeCanyon.
- Version v1.1.0
- Updated Sep 2026
- Platform Flutter
- Stack Flutter · Next.js
- Demo APKscoachly-student-v1.1.0 · 53 MBcoachly-instructor-v1.1.0 · 52.8 MB
On this page
Overview
Run a complete coaching institute — courses, mock tests, batches, analytics, and an AI tutoring layer — on one modern stack. Two Flutter apps + one Next.js project (storefront + admin + backend).
Coachly is a coaching & test-prep LMS sold as source code — a coaching institute runs its whole test-prep business (courses, mock-test series, batches, analytics) on one modern codebase, with four AI features (question generation, answer explanations, adaptive practice, and a doubt-solving tutor) built in. It ships as a 4-surface bundle delivered in 3 apps:
- Student app — Flutter (Android + iOS)
- Instructor app — Flutter (Android + iOS)
- One Next.js app — the public storefront (
/) + the institute admin panel (/admin) + the sole backend (/api/v1)
The wedge over Laravel incumbents: four AI features (not a bolt-on chatbot) with a mandatory instructor review queue, on a modern TypeScript + Flutter stack. Portable: deploy on Docker, Railway, or Vercel, backed by any Postgres (Neon is easiest).
What’s included
- Full source for all three apps — 2 Flutter apps + 1 Next.js project (with the sole backend)
- Drizzle ORM schema (21 tables) + 7 SQL migrations
- The “Academic Precision” design system — shared Tailwind theme + Flutter
ThemeData - Branding pipeline — SVG logo mark → web favicon + Android/iOS launcher icons
- Rich demo seed — demo institute + accounts, an “IELTS Full Mock Series” (4 tests / ~54 questions with cached explanations), graded attempts, 2 batches + announcements, and pending AI review-queue drafts
- Buyer documentation (this guide) with a step-by-step setup + deploy walkthrough (Docker / Railway / Vercel / Neon)
- In-process backend test suites (
verify:auth / verify:assessment / verify:ai / verify:seed) + Flutter unit/widget tests - 6 months Item Support (per Envato Item Support Policy) + free future updates
Features
Student app (Flutter)
- Email/password auth (JWT Bearer); dashboard with enrolled courses + performance rank
- Course list & player — secure Bunny Stream video (signed playback) + notes/PDFs + lesson-progress tracking
- Mock-test series — timed exams with a countdown + auto-submit, instant scoring, results + solutions
- AI answer explanations, adaptive / weak-area practice, and an AI doubt-solving chatbot (a tutor, never a cheat engine)
- Batches & announcements, bookmarks (“Saved”), paywall/enrolment (free enrol + Stripe/Razorpay checkout)
Instructor app (Flutter)
- Dashboard, course builder + lesson editor, test & question-bank builder
- AI question generator + review queue — approve/reject drafts before students ever see them
- Batch management, student performance, earnings, settings
Institute admin (web, /admin)
- Dashboard (enrollments, revenue, KPIs); manage courses/lessons, test series/questions, batches, students, instructors, categories, coupons, payments
- The AI review queue, reports/analytics, and settings (institute branding, payment/AI provider config, course owner + revenue-share)
Public storefront (web, /)
- SSR/SEO catalog, course + batch detail, checkout/enrol, blog, about/contact, sitemap + OpenGraph metadata (canonical origin =
APP_URL)
Backend (Next.js /api/v1)
- REST: auth, courses/tests/attempts, batches,
me/*, bookmarks, rank/leaderboard, AI (explanations, weak-areas, doubt-chat) - The instructor surface
/api/v1/instructor/**(courses/tests/questions/AI drafts/batches/earnings),webhooks/{stripe,razorpay},uploadthing, and an Inngest scaffold
Architecture
coachly/
apps/
web/ Next.js 16 — storefront (/) + admin (/admin) + backend (/api/v1)
app/(storefront)/ public SSR storefront + blog
app/admin/ institute admin panel
app/api/ the backend (/api/v1, webhooks, uploadthing, inngest)
student-app/ Flutter (Riverpod 2 + GoRouter + Dio + freezed)
instructor-app/ Flutter (same stack)
packages/
db/ Drizzle schema + migrations (@coachly/db)
core/ services + Zod DTOs + AI + payments + seed (@coachly/core)
brand/ SVG logo sources (single source of truth)
docs/ PRD.md, DESIGN.md, DEPLOYMENT.md, codecanyon//admin) + JWT Bearer (mobile, /api/v1)AI_PROVIDER)Design invariants: a single backend/DB, thin JWT mobile clients on the /api/v1 contract, all business logic in @coachly/core, money as integer cents server-side, and AI keys server-side only.
API contract (/api/v1)
The Flutter apps talk to the backend over one versioned REST contract. Every response is a { data } | { error } JSON envelope (apps/web/lib/http.ts).
Obtain a token from POST /api/v1/auth/login and send it as Authorization: Bearer <token>. Public catalog reads need no token; me/** routes need a student token; instructor/** routes need an instructor token; webhooks are called by the payment provider, not the apps. Path params are shown as [id].
Auth and profile
Catalog, enrolment and learning
Tests, attempts and practice
Instructor (instructor token)
Webhooks and health
apps/web/app/api/v1/**. Full request/response shapes are the Zod DTOs in @coachly/core. Auth.js also mounts /api/auth/* (web sessions), and /api/uploadthing + /api/inngest serve uploads and background jobs.Database setup
- 01Create a Postgres database (Neon free tier recommended).
- 02Copy
.env.example→apps/web/.env.localand setDATABASE_URL+AUTH_SECRET. - 03Apply migrations:
npm run db:migrate - 04Load the demo data:
npm run db:seed(institute, demo accounts, mock-test series, batches, graded attempts, AI drafts).
db:migrate and skip db:seed. Deployment steps are in the Deployment section below.Database backup and restore
Coachly stores everything in one PostgreSQL database, so a backup is a single logical dump. This uses the standard Postgres client tools — pg_dump and pg_restore (or psql) — no Coachly-specific script needed.
Install them with brew install libpq (macOS) or apt-get install postgresql-client (Debian/Ubuntu). Run every command against your direct (non-pooled) connection string — not the pooler.
- 01
Create a backup
A compressed, portable dump file you own:
terminal# custom-format dump (recommended — compact, restores selectively): pg_dump "$DATABASE_URL" -Fc -f coachly-backup.dump # or a plain-SQL dump you can open and read: pg_dump "$DATABASE_URL" --no-owner --no-privileges -f coachly-backup.sql - 02
Restore
Into a fresh, empty database (or over the existing one;
--cleandrops objects first):terminal# restore a custom-format (.dump) backup: pg_restore --clean --if-exists --no-owner -d "$DATABASE_URL" coachly-backup.dump # restore a plain-SQL (.sql) backup: psql "$DATABASE_URL" -f coachly-backup.sql
cron running the pg_dump above). Restore into an empty database when migrating hosts. On Neon you also get point-in-time restore and branching from the dashboard, but a pg_dump file is a portable, provider-independent backup you keep yourself. Take a fresh backup before running npm run db:migrate on a production database.Environment variables
All config is server-side. Copy .env.example → apps/web/.env.local — the web runtime, drizzle-kit, db:migrate and db:seed all read that one file. Everything is optional except where noted: the app boots keyless and each integration degrades gracefully until its key is set. Server env only — never ship AI / payment / DB keys to the Flutter apps.
coachly.devsnack.dev)openai | geminiopenai.env.example and the validated schema in apps/web/lib/env.ts. FCM_PRIVATE_KEY is a PEM — escape its newlines as \n in the value (the push helper unescapes them at read time). See Deployment → Production hurdles for DATABASE_URL pooling and SSL.Deployment
The Next.js app (storefront + admin + backend) is one deployable. Any Node host + any Postgres works; three common paths:
Docker
A multi-stage Dockerfile ships at the repo root (Node 20, next start). It includes packages/db so migrations/seed can run from the image.
docker build -t coachly .
docker run -p 3000:3000 --env-file apps/web/.env.local coachly
# one-time (against your DB):
docker run --env-file apps/web/.env.local coachly npm run db:migrate
# optional demo data:
docker run --env-file apps/web/.env.local coachly npm run db:seedRailway
New project → deploy from repo (the root Dockerfile is detected) → add a PostgreSQL plugin → set the env vars (at minimum DATABASE_URL, AUTH_SECRET, APP_URL). Run npm run db:migrate once from the service shell (or a one-off command).
Vercel
Import the repo; set the build to the @coachly/web workspace (npm run build) and add the env vars. Migrations run manually, not in the Vercel build — run npm run db:migrate against your database once (locally or via a one-off), then redeploy.
Postgres (Neon)
Create a Neon project and use its connection string as DATABASE_URL (the pooled URL is fine for the app). Run npm run db:migrate to create the schema; optionally npm run db:seed for demo data.
APP_URL to your public origin so OpenGraph/canonical URLs resolve correctly. Payment redirect URLs derive from the live request origin automatically.Production hurdles — connection pooling, SSL and limits
Coachly uses the postgres.js driver (packages/db/src/client.ts), which works with any Postgres. Three things trip up most first deploys:
- Connection pooling. Serverless / edge hosts (Vercel, Lambda) open a fresh connection per invocation and quickly exhaust a small Postgres
max_connections. PointDATABASE_URLat your provider’s transaction-pooling endpoint — Neon’s pooled host (the-poolerhostname), Supabase’s pooler (port6543), or PgBouncer in transaction mode. The client already setsprepare: false, exactly what transaction poolers require (no server-side prepared statements), so the pooled URL works out of the box. On a long-lived Node host (Docker / Railway) the direct URL is fine. - Run migrations on the direct connection. Always run
npm run db:migrateagainst the non-pooled connection string — the migrator uses a single connection (max: 1) and DDL should not go through a transaction pooler. On Neon: migrate on the plain host, run the app on the-poolerhost. To cap the app’s own pool size, addmaxto thepostgres(...)options inpackages/db/src/client.ts. - SSL for PostgreSQL. Managed hosts require TLS. Append
?sslmode=requiretoDATABASE_URL(postgres.js reads it — it is already in.env.example). If your host uses a self-signed or private CA and you hitself-signed certificate in certificate chain, use?sslmode=no-verify(still encrypted, skips CA verification) or supply the CA. A self-hosted Postgres without TLS: omitsslmode— not recommended for production.
FCM_PRIVATE_KEY is a multi-line PEM. In a single-line env value, escape the newlines as \n — the push helper (packages/core/src/notify/push.ts) unescapes them at read time.Optional integrations
Everything runs keyless for dev/demo and gracefully no-ops until you add keys. Each integration below is a separate third-party account you provide and pay for yourself — none is bundled with this item, and each has its own pricing (most offer a free tier to start).
video_not_configured; embeds still worksent: 0ai_not_configured)AI provider setup
Pick a provider via AI_PROVIDER=openai|gemini and set OPENAI_API_KEY or GEMINI_API_KEY (optional model overrides OPENAI_MODEL / GEMINI_MODEL). These are server env only — never in a Flutter build. The four features live behind /api/v1 and call @coachly/core/src/ai/*.
How AI questions reach students — the review queue
AI-generated questions are never auto-published. Every draft passes through a mandatory human review before any student can see it:
- 01
Draft generated
An instructor asks the assistant to draft questions from a topic or uploaded notes. - 02
Held in the review queue
Every draft is stored inaiQuestionDrafts— students never see it. - 03
A human reviews
An instructor opens each draft and checks the question and its correct answer. - 04
Approve or reject
- Approve → joins the live question bank
- Reject → discarded, never published
- 05
Student sees
Only approved questions ever appear in a test.
Guardrails (server-enforced):
- AI-generated questions never auto-publish — they land in
aiQuestionDraftsfor instructor review; approval is the only path into the live question bank. - The doubt chatbot and explanations refuse to serve answers while the student has an in-progress attempt on that test (
ai/guards.ts). - System prompts are centralized and server-enforced (not client-editable).
Payments setup
- 01
Stripe
SetSTRIPE_SECRET_KEY; add a webhook athttps://<deploy>/api/v1/webhooks/stripeand pasteSTRIPE_WEBHOOK_SECRET. Checkout returns a hosted-redirect URL; the webhook confirms and creates the enrolment. - 02
Razorpay
SetRAZORPAY_KEY_ID+RAZORPAY_KEY_SECRET(+RAZORPAY_WEBHOOK_SECRET); uses Payment Links (hosted redirect) +/api/v1/webhooks/razorpay. - 03
Keyless stub
With no gateway keys, enrolment completes inline (great for demo/dev). Free (or fully coupon-discounted) courses always enrol without payment.
Refunds are real: an admin refund issues the Stripe/Razorpay refund. Money is integer cents, computed server-side only.
Secure video, uploads and push
Secure video (Bunny Stream)
Set BUNNY_STREAM_LIBRARY_ID + BUNNY_STREAM_TOKEN_KEY (+ BUNNY_STREAM_API_KEY). Upload videos in the Bunny dashboard, then paste the playback URL into a lesson’s Video URL (admin → course → lesson). The backend mints a short-lived signed playback URL and checks the student’s enrolment before handing it out (media/video.ts). Free fallback: paste a YouTube / Vimeo / HLS embed URL (no signing needed).
Uploads (UploadThing)
Set UPLOADTHING_TOKEN to enable in-app upload buttons on the admin course/lesson/resource forms (with a paste-URL fallback that keeps working keyless). Notes/PDFs + images, staff-gated.
Push (FCM)
Set FCM_PROJECT_ID + FCM_CLIENT_EMAIL + FCM_PRIVATE_KEY (service-account creds; the private key is \n-escaped). The backend actually sends via firebase-admin (notify/push.ts); the apps only hold FCM tokens.
inngest package is intentionally not a dependency yet; jobs run inline). Wiring it at go-live via INNGEST_* is the documented path (roadmap).Branding and customization
- Logo (source of truth):
brand/*.svg. Edit, regenerate the PNGs perbrand/README.md(macOSqlmanage+sips), then rundart run flutter_launcher_iconsin each Flutter app. The web faviconapps/web/app/icon.svgis served directly. - Theme tokens: web —
apps/web/tailwind.config.ts+apps/web/app/globals.css; Flutter —apps/*/lib/theme. Keep values identical (one system). Institute branding is also editable at runtime in admin Settings. - App name + ids: each
pubspec.yaml,android/app/build.gradle.kts(applicationId+namespace),AndroidManifest.xml(android:label),ios/Runner/Info.plist(CFBundleDisplayName), and the pbxprojPRODUCT_BUNDLE_IDENTIFIER. Defaults:com.devsnack.coachly.student/com.devsnack.coachly.instructor, names “Coachly” / “Coachly Instructor”. - Multi-language / RTL: not wired today (roadmap, PRD §12).
admin-guide.html) → Branding shows exactly where each field lives, with a screenshot and step-by-step instructions for uploading your logo.Production checklist
- 01Branding: edit
brand/*.svg→ regenerate →dart run flutter_launcher_iconsin each app. - 02Theme tokens:
tailwind.config.ts+globals.css+ Flutter theme. - 03App names + ids in the Flutter pubspecs + native config.
- 04Provision Postgres (Neon recommended).
- 05
npm install→npm run db:migrate(→ optionalnpm run db:seed). - 06Set env:
DATABASE_URL,AUTH_SECRET,APP_URL+ any integrations. - 07Deploy the web (Docker / Railway / Vercel — see the Deployment section).
- 08Add Stripe/Razorpay webhooks; set Bunny / UploadThing / FCM keys.
- 09Add a per-user AI rate cap before exposing AI publicly.
- 10Build Flutter releases with
--dart-define=API_BASE_URL=<your api>. - 11Change the demo passwords.
Changelog
Every version published so far. Updates are free for the life of the item.
Version 1.0.0 · July 7, 2026
- Initial release: student + instructor Flutter apps, Next.js storefront + admin + backend, four AI features with the instructor review queue, Stripe + Razorpay, Bunny video, UploadThing, Resend, FCM, and a rich demo seed.
Support and licensing
Preferred contact method: email is the primary channel. For quick questions you can also message WhatsApp.
What support covers
- Six months of support from purchase, extendable at checkout
- Support covers bugs in the template and questions about how it is put together
- It does not cover custom feature work, third-party API changes or store review outcomes
- The Regular licence covers one free end product. Charging users for the app itself needs the Extended licence