Gym CRM SaaS: Multi-Tenant Gym Management on Next.js + Supabase (Open Source)
TL;DR: An open-source, multi-tenant gym CRM built on Next.js 14, TypeScript and Supabase. One codebase serves many isolated gyms, with separate panels for owners, trainers, members and a superadmin; 18 database tables and 35 Row Level Security policies; memberships, attendance, payments, workout and diet plans, lead follow-ups, and a WhatsApp "tap-to-send" retention system with localised templates. Configure once, run with npm run dev.
The problem: gyms run on spreadsheets and memory
Walk into most independent gyms and the "CRM" is a paper register at the front desk, a spreadsheet of memberships, and the owner's memory of whose fees are due. Members churn silently — nobody notices the regular who stopped coming three weeks ago. Trainers assign workouts on WhatsApp threads. Renewals depend on someone remembering to ask.
Existing gym software tends to be either single-location desktop tools or expensive per-location SaaS with rigid pricing. A multi-tenant open-source platform changes the economics: one deployment, many gyms, each isolated from the others, and the gym owner can configure pricing, plan durations and reminder rules instead of hardcoding them into someone else's product.
What it is
Gym CRM SaaS is a production-structured Next.js 14 application (App Router, TypeScript, Tailwind CSS) with Supabase as the backend — PostgreSQL for data, Supabase Auth for identity, and Postgres Row Level Security for tenant isolation. The repository is a single deployable app, not a monorepo: src/app for routes, src/lib for Supabase clients, hooks and utilities, and supabase/schema.sql as the one-command database setup.
Four roles, four panels
Role-based access is structural, not cosmetic — each role gets its own route subtree:
| Role | Routes (verified in the tree) | What they do |
|---|---|---|
| Owner | owner/dashboard, members, leads, payments, plans, trainers, attendance, retention, settings | Full gym operations: member roster, lead pipeline, billing, plan catalogue, staff, and a dedicated retention view for at-risk members |
| Trainer | trainer/dashboard, members, workouts, diets | Their assigned members, workout-plan authoring, diet-plan authoring |
| Member | member/dashboard, workout, diet, attendance, payments, profile, renew | Self-service: today's workout and diet, attendance history, payment records, profile, and membership renewal |
| Superadmin | superadmin/dashboard | Platform-level view across gyms — the multi-tenant control plane |
Session handling is done the Supabase-SSR way: src/middleware.ts runs on every request (except static assets) and calls updateSession from src/lib/supabase/middleware.ts, keeping auth cookies fresh across server components and preventing the stale-session logouts that plague naive Supabase integrations. Separate client/server Supabase helpers live in src/lib/supabase/, and generated database types sit in types.ts.
Under the hood: the data model
supabase/schema.sql is a ~27KB schema I read table by table. The 18 tables tell the product story better than any marketing copy:
- Tenancy:
gyms,gym_subscriptions,subscription_plans(the SaaS layer — gyms subscribe to plans),profiles(users),gym_settings(per-gym configuration). - Memberships:
membership_plans(the catalogue),members,member_memberships(who holds what, when it lapses). - Operations:
attendance(check-ins),payments(billing records),trainers. - Programming:
workout_plans+workout_plan_items,diet_plans+diet_plan_items— plans with line items, authored by trainers, consumed by members. - Growth:
leads+lead_followups(a real lead pipeline with follow-up tracking, not just a contact list), andmessage_templates— the template store behind the WhatsApp retention system.
Two engineering choices stand out. First, 35 Row Level Security policies: tenant isolation is enforced in Postgres itself, so a bug in application code can't leak one gym's members to another. Second, zero hardcoded business numbers — pricing, durations, and reminder rules live in gym_settings and are editable per gym, which is the difference between software and a configurable product.
The frontend stack is pragmatic and current: react-hook-form + zod for validated forms, recharts for dashboards, date-fns for date logic, lucide-react icons, and React Context plus Server Components for state. Small focused utilities — src/lib/utils/whatsapp.ts (the tap-to-send message builder), dates.ts, formatters.ts — and useAuth / useSettings hooks keep the pages thin.
The member experience: self-service that reduces front-desk load
The member panel is the quiet cost-saver in the system. Members get their own dashboard with today's workout plan (exercises, sets, reps from workout_plan_items), their diet plan for the day, attendance history, payment records, and profile management — plus a renew route for membership renewal. Every routine question a member would otherwise ask at the front desk — "what's my workout today?", "when does my plan expire?", "did my payment go through?" — is answerable without staff involvement. For a gym owner, that is the difference between a receptionist who processes renewals all day and one who sells personal training.
The trainer panel mirrors this from the coaching side: trainers see their assigned members, author workout plans with line items, and maintain diet plans — all scoped to their gym by the same RLS layer. The data flows one way that matters: trainer-authored plans become member-visible routines, attendance records feed the retention view, and payment records feed the revenue picture on the owner dashboard. Nothing is a dead screen; every panel reads from and writes to the shared schema.
Code-level specifics worth noting
- Supabase SSR done properly: three client variants (
client.tsfor browser,server.tsfor server components,middleware.tsfor session refresh) follow the official@supabase/ssrpattern instead of a single leaky client — the most common source of auth bugs in Next.js Supabase apps. - Generated types:
src/lib/supabase/types.tscarries the database types, so queries against the 18 tables are type-checked at build time. Schema drift surfaces as a compiler error, not a runtime surprise. - Form validation at the boundary:
react-hook-formpaired withzodschemas means member sign-ups, plan creation, and payment entries are validated before they touch the database — cheap insurance against the garbage data that silently corrupts small-business systems. - Thin pages, small utilities: date logic in
dates.ts, display formatting informatters.ts, auth state in theuseAuthhook, gym configuration inuseSettings. The pages stay readable because the logic lives in named, testable units. - Charts where they earn their keep:
rechartson the owner dashboard for the numbers an owner actually checks — memberships, revenue, attendance trends — rather than decorative widgets.
How to run it
- Set up the database: paste
supabase/schema.sqlinto the Supabase SQL editor and run it — tables, RLS policies and all. - Configure the environment: copy
.env.exampleto.env.localand add your Supabase URL and keys. - Run locally:
npm installthennpm run dev.DEPLOYMENT.mdcovers full deployment.
The dependency list in package.json is refreshingly lean — no sprawling UI kit, no state-management framework, just the pieces the product needs (next 14.1.0, @supabase/ssr, react-hook-form, zod, recharts, Tailwind).
Honest status and roadmap
Working. The repository is a complete, coherent v1: the role panels exist as real routes, the schema is comprehensive and internally consistent (plans → memberships → payments → attendance all join up; leads → followups → members closes the acquisition loop), and tenant isolation is enforced at the database level rather than trusted to the UI. The WhatsApp retention angle — templates plus tap-to-send, aimed at win-back — is the feature most gym software in this segment doesn't have.
What it isn't yet: billing records exist as data, but production payment collection (gateway integration, webhooks, dunning) is the obvious next layer; there's no member-facing mobile app; and multi-gym analytics for the superadmin is a dashboard route, not a finished BI surface. Sensible roadmap from the schema's own shape: payment-gateway wiring against the payments table, automated renewal reminders driven by member_memberships expiry, and a PWA for members built on the existing member routes.
Frequently asked questions
How is multi-tenancy enforced?
Each gym is a row in gyms; every tenant-scoped table carries the gym reference, and 35 Postgres RLS policies filter all access by the authenticated user's gym. Isolation is a database guarantee, not application logic.
Can a non-technical gym owner use it?
The owner panel is the target UI, and business rules (pricing, durations, reminders) are configurable in settings rather than code. Deployment and Supabase setup still need someone technical — that's the self-hosting trade-off.
What does the WhatsApp retention system do?
Message templates stored in the database, localised per gym, composed with member data and sent via tap-to-send — designed for win-back and renewal nudges to members going quiet.
What does it cost to run?
The code is open source. Runtime cost is a Supabase project (free tier covers small gyms) plus wherever you host the Next.js app — Vercel, a VPS, or similar.
The bottom line
This is a real vertical SaaS codebase, not a tutorial project: four role panels, an 18-table schema with database-level tenant isolation, and the two features that actually decide gym software — renewals and retention — designed in from the start. If you run gyms, sell to gyms, or want a reference architecture for multi-tenant Supabase apps, it's worth reading the schema before anything else.
Repository: github.com/Shivay00001/gym-crm-saas · Want vertical SaaS built for your industry? VisionQuantech designs and deploys custom CRM and automation platforms. Contact: [email protected]