This module stores the text and switches for trainee push notifications at three levels:
  1. Platform defaults. One NotificationTypeConfig row per type, edited by Perform admins.
  2. Studio overrides. One StudioNotificationConfig row per studio and type, edited by the studio owner or head coach. An override replaces the default for that studio.
  3. Trainee preferences. A list of muted groups on Client.mutedNotificationGroups, edited by the trainee in the app.
It does not send anything. Other modules read these settings when they send a push. Source: backend/apps/core-api/src/modules/notification-configs/ (routes, controller, service, repository, schema) and backend/apps/core-api/src/lib/notification-messages.ts for the message helpers and the normalizer.

Mounting and auth

The web lane is serviceAuth, webUserContext, webLimiter. Write routes add requireRole(StudioRole.OWNER, StudioRole.HEAD_COACH), which throws FORBIDDEN (403) with insufficient role for anyone else. See API overview. authenticateTrainee (middleware/trainee-auth.ts) expects Authorization: Bearer <trainee token> and verifies it with BETTER_AUTH_SECRET. It rejects with UNAUTHORIZED (401) when the token is missing or invalid, or when the studio no longer exists. A preview session can only make GET requests. Any other method gets FORBIDDEN (403) with details.reason PREVIEW_READ_ONLY. These two trainee routers do not apply requireTraineeAppAccess, so a trainee whose app access is paused can still call them.

Notification types

notificationConfigType in the schema lists 15 values. Twelve are active (ACTIVE_NOTIFICATION_TYPES). The other three are retired: they stay in the enum and have defaults, but every list endpoint filters them out.

Active types

These columns come from NOTIFICATION_CATALOGUE and NOTIFICATION_CONFIG_DEFAULTS. Variables are written in templates as {{name}}. The 6 hour throttle is PLAN_UPDATE_THROTTLE_HOURS. The default title and body texts are in Hebrew and live in NOTIFICATION_CONFIG_DEFAULTS. The categories, in order, are workout, forms, plans, subscription, messages (NOTIFICATION_CATEGORIES).

Retired types

FORM_ASSIGNED, CHECK_IN_DUE and CHECK_IN_TOMORROW are not in ACTIVE_NOTIFICATION_TYPES.

Kinds and messages

Every setting is exposed as a list of messages (notificationMessage in the schema).
  • single. One message with the fixed id default. It is built from the row’s titleTemplate and bodyTemplate. The stored messages column is not used.
  • formSent. Up to 3 messages, each for a form type. pickFormSentMessage picks the enabled message whose formType matches the form, and falls back to the enabled ALL message. The defaults are one CHECK_IN message and one ALL message.
  • formReminders. Up to 10 reminders, each with a day offset from the form’s day and a time. The default is one reminder the day before a check-in at 19:00 that opens the home screen.
For the two multi-message kinds, messagesOf reads the stored messages JSON. When it is missing, empty or does not parse, the type’s default messages are used.

Preference groups

A trainee mutes groups, not types. notificationPreferenceGroup has six values, in this order: COACH_MESSAGES is locked (LOCKED_NOTIFICATION_GROUPS). A trainee cannot switch it off, and isNotificationMuted ignores it even if it somehow lands in the muted list. notificationGroupFor(type, formType) decides the group. For FORM_SENT and FORM_REMINDERS the form type decides: CHECK_IN maps to CHECK_INS, anything else, including no form type, maps to FORMS. Every other type uses the fixed NOTIFICATION_TYPE_GROUPS map.

Platform defaults (admin)

Both admin endpoints first call ensureDefaults, which upserts a default row for each of the 12 active types. Existing rows are left untouched.

GET /v1/admin/notification-configs

Lists the platform default row for every active type, sorted by type. Auth: serviceAuth only. Response: 200 with an array of NotificationTypeConfig rows.

PATCH /v1/admin/notification-configs/:type

Updates one platform default. Auth: serviceAuth only.
string
required
Any of the 15 notificationConfigType values.
boolean
Master switch for the type.
string
At least 1 character.
string
May be empty.
boolean
Whether the push plays a sound.
string
HH:MM, 24 hour.
Only the fields sent are changed. For FORM_SENT and FORM_REMINDERS, the service also rewrites the first stored message so the two representations agree: its title takes titleTemplate, its subtitle takes bodyTemplate, and its time takes sendTime when that message already has a time. Response: 200 with the updated row, in the same shape as the list. Errors:

Studio overrides, flat shape (web)

These two endpoints expose one flat row per type. They predate the multi-message settings and stay for callers that only need title, body and time.

GET /v1/web/notification-configs

Lists the effective config for each active type in the caller’s studio. Auth: web lane, any role. For each active type the studio override is returned when one exists, otherwise the platform default. customized says which one it is. Response: 200.

PATCH /v1/web/notification-configs/:type

Creates or updates the studio’s override for one type. Auth: web lane, roles OWNER or HEAD_COACH.
string
required
A notificationConfigType value.
boolean
Switch for this studio.
string
At least 1 character.
string
May be empty.
boolean
Whether the push plays a sound.
string
HH:MM, 24 hour.
The service starts from the studio’s current effective values, applies the fields sent, and upserts the StudioNotificationConfig row. A studio’s first PATCH therefore copies every other field from the platform default into the override. Later changes to the platform default no longer reach that studio for that type. For the multi-message kinds, the first message is rewritten the same way as in the admin PATCH. Response: 200.
messages is included only for FORM_SENT and FORM_REMINDERS. Errors: VALIDATION (422), FORBIDDEN (403) insufficient role, UNAUTHORIZED (401). The route accepts retired types too. For one of those the service writes an override built from the code defaults, but no list endpoint ever returns it.

Studio settings, structured shape (web)

This is the shape the notification settings screen uses. Each setting carries its catalogue metadata, its messages and the platform defaults to reset to.

GET /v1/web/studio/notification-settings

Returns every active type with its messages, plus the category order. Auth: web lane, any role. Response: 200. Settings come in ACTIVE_NOTIFICATION_TYPES order.
enabled, sound and messages are the studio’s effective values. defaults always reflects the platform row.

PUT /v1/web/studio/notification-settings/:type

Replaces the studio’s whole setting for one type. Auth: web lane, roles OWNER or HEAD_COACH.
string
required
Must be an active type.
boolean
required
Switch for the whole type in this studio.
boolean
required
Whether the push plays a sound.
object[]
required
1 to 20 messages in the notificationMessage shape described above. The per-type limit is checked after parsing.
normalizeSetting cleans and checks the messages:
  • title has line breaks collapsed to a space and is trimmed. It must not be empty.
  • subtitle is trimmed.
  • Message ids must be unique.
  • single: the message id becomes default and the message is always enabled. Extra fields are dropped.
  • formSent: formType defaults to ALL. Only id, enabled, title, subtitle and formType are kept.
  • formReminders: formType defaults to CHECK_IN. offsetDays is required and must be between -3 and 3. A negative offset (before the form’s day) is only allowed for CHECK_IN. time is required and must be HH:MM. onlyIfNotFilled is forced to false for a negative offset and defaults to true otherwise. opens defaults to form. Reminders are sorted by offsetDays, then time.
The stored override keeps titleTemplate, bodyTemplate and sendTime in step with the first message. messages is stored only for the multi-message kinds. Response: 200 with the normalized setting.
Errors: Issue codes from the normalizer: There is no endpoint to delete an override. To go back to the platform text, send the values from defaults. The setting stays customized: true.

Trainee endpoints

GET /v1/trainee/notification-configs

Returns the effective config per active type for the trainee’s studio, with the trainee’s mutes applied. The app uses it for notifications it schedules on the device. Auth: trainee bearer token. enabled is the studio’s switch and not muted by the trainee. The mute check here calls isNotificationMuted(type, muted) with no form type, so both FORM_SENT and FORM_REMINDERS follow the FORMS group in this list. Response: 200.
messages and customized are not included. Errors: UNAUTHORIZED (401), NOT_FOUND (404) trainee not found.

GET /v1/trainee/notification-preferences

Lists the groups the trainee can control. Auth: trainee bearer token. A group is listed only when at least one enabled type in the studio feeds it:
  • A single-kind type adds its fixed group.
  • A multi-message type adds a group per enabled message. A message with formType ALL, or none, adds both FORMS and CHECK_INS. CHECK_IN adds CHECK_INS. INTAKE adds FORMS.
Groups come back in enum order. A locked group is always reported as enabled. Response: 200.
Errors: UNAUTHORIZED (401), NOT_FOUND (404) trainee not found.

PATCH /v1/trainee/notification-preferences

Switches one group on or off for the trainee. Auth: trainee bearer token. Refused for preview sessions.
string
required
One of CHECK_INS, FORMS, PLANS, WORKOUT_REMINDERS, COACH_MESSAGES, SUBSCRIPTION.
boolean
required
false adds the group to Client.mutedNotificationGroups. true removes it.
A group can be muted even when it is not currently listed for the studio. Response: 200 with the same shape as the GET, after the change. Errors:
  • Forms for the form types that FORM_SENT and FORM_REMINDERS messages target.
  • Coach assistant sends TRAINER_MESSAGE pushes and respects the studio switch.