Wellness & Beauty
The Wellness vertical powers spas, salons, gyms, yoga studios, and beauty brands with three integrated systems: practitioner booking, tiered memberships, and client wellness profiles. Malet Owners configure these features via the Storefront Window layout system.
NOTE
These features live in the experiences subgraph โ the same cross-vertical Feature Plane that houses Entertainment & Experiences and Professional Services. The architecture is documented in docs/architecture/18-progressive-vertical-extraction.md.
Architecture: Two-Plane Design
The Wellness vertical uses the same Control + Feature plane separation as Entertainment and Professional:
- Control Plane (
maletssubgraph): VerticalConfig feature flags decide which Wellness modules are enabled per Malet โpractitionerBooking,membershipEngine,productServiceBundles,clientProfiles. - Feature Plane (
experiencessubgraph): Self-containedMembershipModuleandWellnessProfileModule. Each can be extracted to its own subgraph when a dedicated Wellness vertical Team forms. - Extensions (
servicessubgraph): Practitioner-specific fields on existing Service and Booking entities.
Three new Storefront Window layout slot types are available: PRACTITIONER_GRID, MEMBERSHIP_PLANS, and BUNDLE_SHOWCASE.
Membership Engine
Create tiered membership plans offering session quotas, product discounts, and perks. Visitors subscribe and track usage in real-time.
Plan Tiers
| Tier | Typical Use |
|---|---|
BRONZE |
Entry-level โ limited sessions, no product discount |
SILVER |
Mid-tier โ moderate sessions, small product discount |
GOLD |
Premium โ generous quotas, significant discounts |
PLATINUM |
VIP โ unlimited sessions, maximum perks |
TIP
The tier enum is named WellnessPlanTier (not MembershipTier) to avoid GraphQL Federation composition conflicts with the existing MembershipTier in the nodes subgraph.
Subscription Lifecycle
ACTIVE โ PAUSED โ ACTIVE
โ CANCELLED
โ EXPIRED
Subscriptions start as ACTIVE when a Visitor subscribes. They can be PAUSED for temporary holds (e.g., vacation), CANCELLED by the Visitor, or automatically EXPIRED when the billing period lapses without renewal.
Mutations
# Create a membership plan (Malet Owner)
mutation {
createMembershipPlan(
maletId: "m_serenity_spa"
name: "Gold Wellness Membership"
tier: GOLD
sessionsPerMonth: 8
monthlyPrice: 14900 # $149.00 (cents)
productDiscountPercent: 20
) {
id
name
tier
sessionsPerMonth
productDiscountPercent
isActive
}
}
# Deactivate a plan (soft-delete โ no new subscriptions)
mutation {
deactivateMembershipPlan(planId: "plan_1") {
id
isActive
}
}
# Subscribe to a plan (Visitor)
mutation {
subscribeMembership(maletId: "m_serenity_spa", planId: "plan_1") {
id
status
sessionsRemaining
currentPeriodStart
currentPeriodEnd
}
}
# Record a session usage (after booking)
mutation {
recordMembershipUsage(subscriptionId: "sub_1") {
id
sessionsUsed
sessionsRemaining
}
}
# Pause membership (Visitor)
mutation {
pauseMembership(subscriptionId: "sub_1") {
id
status
}
}
# Resume membership
mutation {
resumeMembership(subscriptionId: "sub_1") {
id
status
sessionsRemaining
}
}
# Cancel membership
mutation {
cancelMembership(subscriptionId: "sub_1") {
id
status
}
}
# Renew period โ reset usage counters (Malet Owner or cron)
mutation {
renewMembershipPeriod(subscriptionId: "sub_1") {
id
sessionsUsed
sessionsRemaining
currentPeriodStart
currentPeriodEnd
}
}
Queries
# All active plans for a Malet (public โ storefront display)
query {
membershipPlans(maletId: "m_serenity_spa") {
id
name
tier
sessionsPerMonth
productDiscountPercent
monthlyPrice
features
}
}
# Visitor checks their active membership
query {
myMembership(maletId: "m_serenity_spa") {
id
planId
status
sessionsUsed
sessionsRemaining
currentPeriodEnd
}
}
# Malet Owner views all subscribers
query {
maletMembershipSubscriptions(maletId: "m_serenity_spa", status: ACTIVE) {
id
memberId
planId
sessionsUsed
sessionsRemaining
}
}
Session Usage Tracking
Usage is tracked atomically using MongoDB $inc operators:
sessionsUsedincrements by 1sessionsRemainingdecrements by 1 (unless unlimited)- If
sessionsRemainingreaches 0, further bookings are blocked with aBadRequestException - Plans with
sessionsPerMonth: 0grant unlimited sessions (sessionsRemainingis set to-1)
Client Wellness Profiles
Track client health data, preferences, and practitioner treatment notes across sessions. Each profile is scoped to a single Malet โ a Visitor visiting multiple Wellness Malets has separate profiles per Malet.
Skin Types
| Type | Description |
|---|---|
OILY |
Excess sebum production |
DRY |
Lacks moisture, may flake |
COMBINATION |
Oily T-zone, dry cheeks |
SENSITIVE |
Reacts to products, redness |
NORMAL |
Balanced, minimal concerns |
Mutations
# Complete intake form (Visitor) โ upserts on (maletId, clientId)
mutation {
upsertWellnessProfile(
maletId: "m_serenity_spa"
allergies: ["latex", "eucalyptus"]
skinType: SENSITIVE
bodyFocus: ["lower back", "shoulders"]
preferences: "Prefers firm pressure. Avoid scented oils."
) {
id
allergies
skinType
bodyFocus
intakeFormCompleted
}
}
# Add treatment note (Practitioner)
mutation {
addTreatmentNote(
profileId: "wp_1"
content: "Used hypoallergenic oil. Client reported reduced tension in lower back after 60min deep tissue."
isPrivate: false
bookingId: "bk_42"
) {
id
treatmentNotes {
practitionerId
content
isPrivate
createdAt
}
}
}
# Add private note (only practitioner + Malet Owner can view)
mutation {
addTreatmentNote(
profileId: "wp_1"
content: "Observe possible stress-related tension pattern. Recommend weekly sessions."
isPrivate: true
) {
id
treatmentNotes {
content
isPrivate
}
}
}
Queries
# Visitor checks their own profile
query {
myWellnessProfile(maletId: "m_serenity_spa") {
id
allergies
skinType
bodyFocus
preferences
intakeFormCompleted
treatmentNotes {
content
createdAt
}
}
}
# Malet Owner views all client profiles
query {
maletWellnessProfiles(maletId: "m_serenity_spa") {
id
clientId
allergies
skinType
intakeFormCompleted
treatmentNotes {
content
practitionerId
isPrivate
}
}
}
# Single profile with privacy filtering
query {
wellnessProfile(id: "wp_1", isOwnerOrPractitioner: true) {
id
allergies
treatmentNotes {
content
isPrivate
practitionerId
}
}
}
Treatment Note Privacy
Each TreatmentNote has an isPrivate flag:
- Public notes (
isPrivate: false): Visible to the Visitor, practitioner, and Malet Owner โ useful for aftercare recommendations - Private notes (
isPrivate: true): Visible only to the authoring practitioner and the Malet Owner โ useful for clinical observations the client shouldn't see
When a Visitor queries their own profile, private notes are automatically filtered out.
Practitioner Booking
Extended booking fields for wellness services on the existing services subgraph:
Service Fields
| Field | Type | Description |
|---|---|---|
practitionerIds |
[String] |
Organization member IDs who can perform this service |
requiresIntakeForm |
Boolean |
Whether a wellness intake form is needed before booking |
Booking Fields
| Field | Type | Description |
|---|---|---|
practitionerId |
String |
The specific practitioner assigned to this booking |
treatmentNotes |
String |
Post-session practitioner notes (linked back to WellnessProfile) |
Example: Create a Practitioner-Linked Service
mutation {
createOneService(
input: {
service: {
name: "60min Deep Tissue Massage"
description: "Therapeutic deep tissue work targeting chronic tension"
price: 9500 # $95.00 (cents)
maletId: "m_serenity_spa"
practitionerIds: ["staff_a", "staff_b", "staff_c"]
requiresIntakeForm: true
}
}
) {
id
name
practitionerIds
requiresIntakeForm
}
}
Booking Flow
- Visitor selects service โ sees available practitioners from
practitionerIds - Visitor picks a practitioner โ availability checked against their schedule
- If
requiresIntakeFormis true, the Visitor must complete theirWellnessProfilefirst - Booking is created with
practitionerIdset to the chosen practitioner - After the session, the practitioner adds
treatmentNotesto both the Booking and the client'sWellnessProfile
TIP
Practitioners are Organization members โ their profiles, specialties, and availability are managed via the existing org โ team โ member hierarchy. No separate "Staff" entity is needed.
Workroom Steps
The Wellness vertical ships with 4 default Workroom steps for treatment lifecycle management:
| Step | Type | Assignee | Description |
|---|---|---|---|
| Health Intake Form | FORM |
Client | Health questionnaire: allergies, skin type, body focus, and preferences |
| Practitioner Assignment | APPROVAL |
Malet Owner | Match the right practitioner based on service and client needs |
| Treatment Notes | FORM |
Malet Owner | Record treatment notes, observations, and aftercare recommendations |
| Product Recommendations | FORM |
Malet Owner | Suggest follow-up products based on the treatment performed |
These steps are automatically configured on new Wellness Malets via the vertical seed (teal/emerald branding).
Gamified Loyalty
Wellness Malets automatically gain access to the Gamified Loyalty system from the Entertainment vertical. Visitors earn points and badges for:
- Completing bookings
- Purchasing recommended products
- Maintaining active memberships
- Referring friends
Tier promotion (Bronze โ Silver โ Gold โ VIP) is handled automatically by the existing loyalty engine. Configure loyalty via the gamifiedLoyalty feature flag.
Deferred Features
The following capabilities are planned for future phases using in-house, open-source solutions โ no third-party SaaS dependencies:
| Feature | Phase | Approach |
|---|---|---|
| Product-Service Bundles | Phase 2 | New Bundle entity in products subgraph linking Product IDs + Service IDs with bundle pricing |
| Stripe Auto-Renewal | Phase 2 | Stripe Billing API on MembershipSubscription.stripeSubscriptionId |
| Subscription Boxes | Phase 2 | Monthly curated product shipments via scheduled Murchase creation |
| Post-Treatment Upsell | Phase 2 | relatedProducts: string[] on Service entity for product recommendation cards |
TIP
The productServiceBundles feature flag is already present in the Control Plane โ it just needs a corresponding BundleModule in the Feature Plane when Phase 2 begins.
Frontend Implementation
Three Svelte 5 storefront widgets render the Wellness vertical in the Visitor-facing storefront:
Components
| Widget | File | Description |
|---|---|---|
WellnessProfile |
src/lib/components/wellness/WellnessProfile.svelte |
Displays client profile โ skin type, allergies, body focus areas, preferences, intake form status, and treatment notes timeline. Auth-gated. |
MembershipPlans |
src/lib/components/wellness/MembershipPlans.svelte |
Tiered plan cards (Bronze/Silver/Gold/Platinum) with pricing, sessions/mo, product discounts, and features list. Active subscription card with session usage bar, billing period countdown, and low-session warnings. |
PractitionerBooking |
src/lib/components/wellness/PractitionerBooking.svelte |
Service category cards (Massage, Facial, Hair, Wellness) linking to the Malet booking page. |
File Map
| File | Purpose |
|---|---|
src/lib/queries/wellness.ts |
GraphQL queries (GET_WELLNESS_PROFILE, GET_MEMBERSHIP_PLANS, GET_MY_MEMBERSHIP) + TypeScript interfaces |
src/lib/utils/wellnessUtils.ts |
13 utility functions โ skin type labels/icons, plan tier badges/colors, session tracking, subscription status, pricing formatters |
src/lib/components/storefront/WidgetRegistry.svelte |
Widget registration: WELLNESS_PROFILE, MEMBERSHIP_PLANS, PRACTITIONER_BOOKING |
tests/wellnessUtils.test.ts |
21 unit tests covering all utility functions |
Layout Slot Types
| Slot Type | Widget | Auth Required |
|---|---|---|
WELLNESS_PROFILE |
WellnessProfile |
โ Yes |
MEMBERSHIP_PLANS |
MembershipPlans |
Public plans, auth for subscription |
PRACTITIONER_BOOKING |
PractitionerBooking |
No |
NOTE
For Visitor-facing documentation on wellness profiles, membership plans, and session tracking, see the Support Manual: Wellness Memberships & Profiles.
Related
- Entertainment & Experiences โ Sibling vertical in the shared
experiencessubgraph Feature Plane; provides gamified loyalty reused by Wellness - Professional Services โ Sibling vertical with a similar client profile pattern (client portals vs. wellness profiles)
- Storefront Sections โ Configure
WELLNESS_PROFILE,MEMBERSHIP_PLANS, andPRACTITIONER_BOOKINGlayout slots - Teams & Sub-Groups โ Organize practitioners with the org โ team โ member hierarchy
- Organization & Malet Management โ Frontend management dashboard where Malet Owners configure their Wellness storefronts
- Culture & Arts โ Sibling vertical reusing the Membership Engine as season passes
- Tech & Electronics โ Sibling vertical; Membership Engine reusable for subscription seat management
- Support: Wellness Memberships & Profiles โ Visitor-facing guide
- Vertical Seeding Infrastructure โ Multi-tenant test data seeding with 4 membership plans, wellness profiles, and subscriptions