Perform stores everything in one PostgreSQL database with two Postgres schemas:
  • auth holds identity and SaaS billing: users, sessions, organizations, members, purchases and coach web notifications. better-auth owns the shape of most of these tables.
  • public holds all domain data: studios, coaches, trainees, programs, libraries, tracking, forms, content, messaging, automation, calendar and CRM.
Both are described by Prisma. There are two Prisma schema files in the backend repo, and they do different jobs. Read The two Prisma schemas before you change a model.

Core entities

Dotted lines are logical links stored as plain ids with no foreign key. They matter because nothing in the database stops them from dangling.

Conventions

Ids

Every model uses a string primary key with @default(cuid()). Two exceptions: FoodBarcodeProduct is keyed by its barcode, and ContentItemTag has a composite key of itemId and tagId.

Tenancy

studioId is the tenant column in the public schema. Repositories always filter by the studioId taken from req.auth, never from the request body. Models fall into four groups: Client scoped tables have no studioId. Any query on them must first prove the client belongs to the caller’s studio.

Per-studio overrides of global rows

Global exercises and foods are never edited in place. A studio’s edit lands in an override table keyed by (studioId, exerciseId) or (studioId, foodId):
  • ExerciseStudioOverride (physical table exercise_video_overrides). Every field is nullable and null means “inherit”.
  • FoodLibraryOverride. A full copy of the editable food fields.
Reads merge the override over the shared row. Because the id stays the same, an override also applies to programs that were already assigned.

Soft delete

Three models carry deletedAt: Studio, Client and Lead. Everything else is hard deleted, mostly through onDelete: Cascade from the studio or the client.

Money

Prices are integers in agorot (1/100 of a shekel): Product.priceAgorot, Client.priceAgorot, ClientSubscription.priceAgorot, Lead.valueAgorot. null means “not set”. 0 is a real price, a free plan.

Dates

Columns typed @db.Date are calendar days with no time: DailyMetric.date, NutritionDayLog.date, CardioLog.performedOn, ClientAppDay.date, Client.birthDate, Client.updateNextOn, Client.updateSkipOn, AvailabilityWindow.date. Everything else is a timestamp. Studio.timezone defaults to Asia/Jerusalem, and Client.timezone holds the trainee device’s zone when the app has reported it.

String columns that behave like enums

Several status columns are plain String validated in code, not Postgres enums. This avoids enum migrations. Examples: AutomationFlowRun.status, AutofitImport.status, AutofitExerciseMatch.status, AdminDeletion.status, ContentItem.type, HomeBanner.source, ClientActivity.actorType, ClientPhoto.pose. The allowed values are listed on each domain page.

JSON columns

Structured documents live in Json columns and are validated by Zod in the API. The large ones have their own pages:

Indexes worth knowing

  • Trigram GIN indexes (gin_trgm_ops, extension pg_trgm) back free-text search on Client.name, Client.phone, Client.email, ExerciseLibraryItem.name, ExerciseLibraryItem.searchText and FoodLibraryItem.name.
  • GIN indexes on array columns: ExerciseLibraryItem.targetMuscles, secondaryMuscles, bodyParts, exerciseTypes, and InboxItem.coachIds.
  • Some constraints exist only in SQL because Prisma cannot express them. See The two Prisma schemas.

Pages by domain

Identity and auth

User, Session, Account, Verification, Passkey, TwoFactor, Organization, Member, Invitation.

Billing

Purchase and the customer ids on User and Organization.

Studios and coaches

Studio, StudioApiKey, Coach, ClientCoach.

Clients, products, subscriptions

Client, Product, ClientSubscription and their status enums.

Programs and templates

ProgramFolder, ProgramTemplate, Program, TraineeExerciseSubstitution.

Exercise and food libraries

Library items, studio overrides, source maps, barcode cache, shopping list.

Tracking

Workout, cardio, daily metrics, health samples, weight, photos, meals.

Forms

Templates, assignments, responses, signing, check-in reviews.

Content and banner

Content items, categories, tags, per-trainee access, home banner.

Messaging and notifications

WhatsApp, push tokens, notification configs, coach notifications.

Automation and tasks

Inbox items, task automations, flow runs, hooks, rules.

Calendar

Events and availability windows.

CRM, audit and misc

Leads, activity feeds, audit log, saved filters, AutoFit import, admin deletions.

The two Prisma schemas

Which file drives what, and how to change a model safely.