Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesOnboardingOnboarding — Data Model

Onboarding — Data Model

Onboarding does not own its own table. It composes writes across existing tables. See parent feature docs for column-level detail; this file maps the effect of the setup sheet on the schema.

Rows created by a successful submit

OrderTableRowSource section
1users(if not yet created by webhook) — findOrCreateFromClerk inside OrganizationsService.create.implicit
2Clerk-sidenew organizationorg section → handleSubmit
3organizationsnew row with name, type, platform_tier='lite', clerk_organization_id.org section → handleSubmit
4membershipsnew row, role='owner', status='active'.org section → handleSubmit (in OrganizationsService.create)
5legal_consentsone row per type in OWNER_LEGAL_DOC_TYPES (terms_of_use today), context='owner_onboarding'.legal section → handleSubmit
6programsone row per ticked train tile, delivery_mode = schedule/coaching, name = programNames[mode] (falls back to a per-locale default if blank). Idempotent — a mode with an existing program is skipped.train section → handleSubmit (onboarding-complete)
7class_typesone row, only when a schedule program was just created (its first class type; name defaults unless onboarding-complete was called separately with classTypeName).train section (indirectly) → handleSubmit
8locationsone row, only for a physical/hybrid org whose ticked modes include schedule.train section (indirectly) → handleSubmit
9(only via a separate onboarding-complete call the sheet doesn’t make)inviteEmails[] → invitations + memberships(pending_invitation).not exercised by the sheet today

Persistence of in-progress state

  • LocalStorage: key taikan:setup-sheet-v5, value JSON-serialized OnboardingState<SetupWizardContext> (flowData plus internal OnboardJS fields; the step id is always sheet). TTL 7 days. No server-side copy. Removed on a successful submit. The key’s version suffix bumps whenever the step shape changes, so a half-finished flow from an older shape starts over rather than resuming into an engine that does not know it. A persisted legalConsentsAccepted=true is overwritten on load (see behavior).
  • No DB row persists progress. The org row exists only after handleSubmit runs.
  • Setup guide: the dismissed flag is the localStorage key taikan:getting-started-dismissed. Step completion is derived live from existing tables (class types, plans, memberships, payment config, workouts, programs); nothing is stored for it.

Tour completion (unused)

  • Column: users.guided_tour_completed_at (timestamptz).
  • PATCH /users/me { guidedTourCompletedAt: <ISO> } still writes it. The web app has no guided tour and neither reads nor sends the field.

Multi-org isolation pattern

Onboarding writes that bootstrap a new org are all scoped to the org being created. The owner row in memberships is the first credential that lets the caller read/write the new org’s data through the standard requireMembership path on subsequent requests.

Soft-delete vs hard-delete

N/A — no onboarding-owned data. Org soft-delete is not exposed; legal consents are immutable (revocation creates a new row).