Studio has two JSON columns. settings is a bag of feature flags, integration state and server-written stamps. branding is the trainee app’s brand skin. Neither has a full Zod schema. Each feature reads its own keys defensively and ignores the rest.

Studio.settings

How it is written

There are two write paths and they behave differently. Other modules write their own sub-objects directly (smartsend, agent, onboarding, demoMode). They read the current object, spread it, and write it back.
PATCH /v1/web/studios/current replaces the blob. A client that sends a partial settings object through it drops every key it did not include, apart from the mbp* keys. Use the settings route for the keys it covers.

Keys validated by patchStudioSettingsBody

Defined in apps/core-api/src/modules/studios/studios.schema.ts.

Changing an anchor re-prices foods

When a patch changes any of the three anchors, patchSettings calls repriceStudioFoods in the same transaction, using the anchors stored just before the change as the “from” values. The response includes a foodRepricing summary, or null when the anchors did not change. This is why the anchors are bounded and why the generic studio update is not allowed to touch them.

onboarding

Server-written stamps about the signup wizard. Read by onboardingOf in billing/billing.repository.ts. Plan limits use these stamps, and never User.onboardingComplete, to decide whether a studio is still in the wizard and exempt from the seat check. See Billing models.

smartsend

State of the studio’s SmartSend WhatsApp connection. Read by readSmartSendSettings in whatsapp/whatsapp.service.ts. One query filters on this path directly: settings with JSON path ['smartsend', 'apiKey']. See SmartSend WhatsApp.

agent

State of the WhatsApp AI assistant for this studio. Written by apps/core-api/src/modules/agent/agent.service.ts. See WhatsApp webhooks and the agent.

demoMode

When true, the hourly demo activity job fabricates trainee activity for the studio so a sales demo looks alive. Never set it on a real studio.

Reading settings safely

Follow the pattern every reader uses:
The column can be null, and nothing guarantees a key’s type. Check the type of each value you read, and choose the default deliberately. Note that calendarEnabled defaults to on and mbpEnabled defaults to off.

Studio.branding

The brand skin of the trainee app. The web panel sends the whole object on every save. The frontend type is Branding in apps/saas/modules/shared/lib/branding.ts. Every key is nullable, and null means “use the default”.

Validation

normalizeBrandingInput in apps/core-api/src/modules/studios/branding.ts passes every key through untouched except two, so no existing blob ever fails to save: Both fail with ErrorCode.VALIDATION and a details entry whose path is ['branding', '<field>'].