Skip to main content

Schemas

warning

RehabAlpha is still under active development. It is not yet HIPAA compliant and should only be used with dummy data.

A schema is a JSON5-based definition that tells RehabAlpha how to render, evaluate, validate, and persist custom documentation data.

At runtime, RehabAlpha uses a schema to:

  • resolve reusable option lists
  • hydrate child references into a tree
  • evaluate routing and visibility conditions
  • generate visible default values
  • build form state
  • convert form state back into effective persisted values
  • prune hidden or empty values before persistence
  • reject stray data keys on the server

This page documents the current node system used by the custom-data runtime.


How schemas are structured

A RehabAlpha schema is authored as a flat array of nodes.

Each node has:

  • id
  • type

Some nodes:

  • render visible content
  • group other nodes
  • route to different child branches
  • store reusable option lists

Nodes are connected by ID references such as:

  • children
  • elseChildren
  • branches[].children
  • fallbackChildren
  • options

A schema may also use inline nested child objects. During parsing, RehabAlpha automatically flattens those objects into separate nodes and replaces them with string ID references.


Default form selection

Schemas can define more than one top-level template node for the same document type.

When RehabAlpha needs a default starting template, it:

  1. finds the templates whose appliesTo matches the current document type
  2. chooses the one with the highest numeric priority

Important details:

  • templates without a numeric priority are ignored for default selection
  • if two matching templates have the same priority, the one that appears earlier in schema order wins
  • if no matching template has a numeric priority, there is no default template
  • showIf does not participate in default template selection

Example:

;[
{
id: 'eval_template_standard',
type: 'template',
label: 'Standard Evaluation',
appliesTo: 'evaluation',
priority: 1,
children: ['standard_eval_group'],
},
{
id: 'eval_template_quick',
type: 'template',
label: 'Quick Evaluation',
appliesTo: 'evaluation',
priority: 5,
children: ['quick_eval_group'],
},
]

In this example, eval_template_quick becomes the default evaluation template because it has the higher priority.

Use higher priority values for the template you want RehabAlpha to select automatically first.


JSON5 support and parser forgiveness

Schemas are written in JSON5, not strict JSON.

That means you can use conveniences like:

  • comments
  • trailing commas
  • unquoted property names when valid in JSON5

RehabAlpha also applies a small amount of parser forgiveness:

  • if you omit the outer array brackets, RehabAlpha can wrap the content for you
  • if you forget commas between top-level object blocks, RehabAlpha can often correct that
  • inline nested child objects are flattened automatically

Nested inline child objects are supported in these child-bearing locations:

  • children
  • elseChildren
  • branches[].children
  • fallbackChildren

Even so, for large schemas, the recommended style is still a clear flat array of nodes.


Hydration and root nodes

When RehabAlpha hydrates a schema:

  • child ID references are resolved into real child nodes
  • options references are resolved and normalized
  • circular references are rejected
  • missing node references are rejected
  • missing options references are rejected
  • non-options references inside options are rejected
  • excessive nesting depth is rejected

options nodes are not treated as renderable roots.

Hydrated roots are simply nodes that:

  • are not options, and
  • are not referenced as a child by another node

Shared concepts

IDs

Every node ID must be unique across the schema.

IDs:

  • cannot be empty
  • must be 100 characters or fewer
  • may contain only letters, numbers, underscores, and dashes
  • cannot both start and end with double underscores

Valid examples:

pain_level
left_knee_rom
assist_level_options

Invalid examples:

''
pain level
pain.level
__reserved__

showIf

showIf is an optional condition supported by visible nodes.

If showIf evaluates to false:

  • that node is hidden
  • its descendants are skipped
  • it does not contribute visible defaults
  • it does not contribute pruned saved values

Important notes:

  • showIf applies to visible nodes such as groups, logic nodes, text nodes, options nodes, and input nodes
  • showIf does not apply to top-level form-definition objects
  • showIf on an options node is currently not used to filter option availability

Example:

{
id: 'pain_level',
type: 'numberInput',
label: 'Pain Level',
showIf: { field: 'has_pain', equals: true },
min: 0,
max: 10,
isInteger: true,
}

Shared input properties

All input nodes share these optional properties:

  • label
  • tooltip
  • isRequiredToSave
  • isRequiredToSign
  • defaultValue

These properties are supported on:

  • textInput
  • textAreaInput
  • dateInput
  • timeInput
  • numberInput
  • checkboxInput
  • selectInput
  • multiSelectInput

Supported node types

Schemas currently support these node types:

Structure and logic nodes

  • group
  • conditional
  • switch
  • multiSwitch

Data and metadata nodes

  • text
  • options

Input nodes

  • textInput
  • textAreaInput
  • dateInput
  • timeInput
  • numberInput
  • checkboxInput
  • selectInput
  • multiSelectInput

Structure and logic nodes

group

A visual container used to organize related content.

Properties

  • id — required
  • type: "group" — required
  • showIf — optional
  • label — optional
  • description — optional
  • children — optional

Notes

  • children may contain string node IDs
  • children may also contain inline child objects during authoring
  • a hydrated group exposes children as hydrated child nodes

Example

{
id: 'history_group',
type: 'group',
label: 'Patient History',
description: 'Review prior level of function and relevant medical history.',
children: ['prior_level', 'surgical_history'],
}

conditional

A logic node with a true branch and an optional false branch.

If when evaluates to true, RehabAlpha traverses children.

If when evaluates to false, RehabAlpha traverses elseChildren, if present.

If the node’s own showIf evaluates to false, neither branch is traversed.

Properties

  • id — required
  • type: "conditional" — required
  • showIf — optional
  • when — required
  • children — optional
  • elseChildren — optional

Notes

  • children may contain string node IDs or inline child objects
  • elseChildren may contain string node IDs or inline child objects
  • a hydrated conditional exposes both as hydrated child nodes

Example

{
id: 'pain_logic',
type: 'conditional',
when: { field: 'has_pain', equals: true },
children: ['pain_level'],
elseChildren: ['no_pain_text'],
}

switch

A mutually exclusive routing node.

RehabAlpha evaluates branches in order and traverses the children of the first matching branch.

If no branch matches, it traverses fallbackChildren, if present.

If the node’s own showIf evaluates to false, no branch or fallback is traversed.

Properties

  • id — required
  • type: "switch" — required
  • showIf — optional
  • branches — required
  • fallbackChildren — optional

Each branch contains:

  • when — required
  • children — optional

Notes

  • branches[].children may contain string node IDs or inline child objects
  • fallbackChildren may contain string node IDs or inline child objects
  • a hydrated switch exposes hydrated branches and hydrated fallback children

Example

{
id: 'discipline_switch',
type: 'switch',
branches: [
{
when: { field: '*disciplineId', equals: 'PT' },
children: ['pt_group'],
},
{
when: { field: '*disciplineId', equals: 'OT' },
children: ['ot_group'],
},
],
fallbackChildren: ['generic_group'],
}

multiSwitch

An inclusive routing node.

RehabAlpha evaluates all branches and traverses the children of every branch that matches.

If the node’s own showIf evaluates to false, no branch is traversed.

Properties

  • id — required
  • type: "multiSwitch" — required
  • showIf — optional
  • branches — required

Each branch contains:

  • when — required
  • children — optional

Notes

  • branches[].children may contain string node IDs or inline child objects
  • a hydrated multiSwitch exposes hydrated branches

Example

{
id: 'payor_logic',
type: 'multiSwitch',
branches: [
{
when: { field: '*payorTypes', includes: 'medicarePartA' },
children: ['pdpm_group'],
},
{
when: { field: '*payorTypes', includes: 'medicaid' },
children: ['medicaid_group'],
},
],
}

Data and metadata nodes

text

Displays static text in the form.

Properties

  • id — required
  • type: "text" — required
  • showIf — optional
  • value — required

Example

{
id: 'gait_note',
type: 'text',
value: 'Assess gait using the least restrictive assistive device possible.',
}

options

Stores a reusable list of selection options.

It is not rendered directly.

Properties

  • id — required
  • type: "options" — required
  • showIf — optional
  • items — required

items

An options node may be authored using either:

  • an array of strings
  • an array of labeled option objects

String example:

{
id: 'side_options',
type: 'options',
items: ['Left', 'Right', 'Bilateral'],
}

Labeled option example:

{
id: 'assist_level_options',
type: 'options',
items: [
{ label: 'Independent', value: 'independent' },
{ label: 'Supervision', value: 'supervision' },
{ label: 'Contact Guard Assist', value: 'cga' },
],
}

Referenced by a selection input:

{
id: 'affected_side',
type: 'selectInput',
label: 'Affected Side',
options: 'side_options',
}

Important notes

  • selection inputs always store the option value, not the label
  • within one authored options array, do not mix strings and labeled objects
  • string options cannot be empty
  • labeled option label values cannot be empty
  • labeled option value values cannot be empty
  • duplicate option values are not allowed
  • hydrated options are normalized into { label, value } objects
  • showIf on an options node is currently not used to filter option availability

Input nodes

textInput

A single-line text field.

Properties

  • id — required
  • type: "textInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • minLength — optional
  • maxLength — optional
  • pattern — optional
  • patternMessage — optional
  • placeholder — optional

Default value type

  • string

Example

{
id: 'chief_complaint',
type: 'textInput',
label: 'Chief Complaint',
placeholder: 'e.g. Right knee pain',
isRequiredToSave: true,
}

textAreaInput

A multi-line text field.

Properties

  • id — required
  • type: "textAreaInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • placeholder — optional

Default value type

  • string

Example

{
id: 'clinical_summary',
type: 'textAreaInput',
label: 'Clinical Summary',
placeholder: 'Enter assessment details...',
}

dateInput

A date picker input.

Values are stored as strings such as YYYY-MM-DD.

Properties

  • id — required
  • type: "dateInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • min — optional
  • max — optional
  • placeholder — optional

Default value type

  • string

Example

{
id: 'date_of_surgery',
type: 'dateInput',
label: 'Date of Surgery',
min: '2020-01-01',
}

timeInput

A time picker input.

Values are stored as strings such as HH:mm.

Properties

  • id — required
  • type: "timeInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • placeholder — optional

Default value type

  • string

Example

{
id: 'time_of_assessment',
type: 'timeInput',
label: 'Time of Assessment',
}

numberInput

A numeric input field.

Properties

  • id — required
  • type: "numberInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • min — optional
  • max — optional
  • step — optional
  • isInteger — optional
  • placeholder — optional

Notes

  • step may be a number or "any"
  • defaultValue may be a number or null

Example

{
id: 'oxygen_sat',
type: 'numberInput',
label: 'O2 Saturation (%)',
min: 0,
max: 100,
isInteger: true,
}

checkboxInput

A boolean input.

Properties

  • id — required
  • type: "checkboxInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional

Default value type

  • boolean

Example

{
id: 'patient_refused',
type: 'checkboxInput',
label: 'Patient refused treatment today',
defaultValue: false,
}

selectInput

A single-select input.

Properties

  • id — required
  • type: "selectInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • flavor — optional
  • options — required
  • placeholder — optional
  • showExplicitNone — optional

options

Must be either:

  • an inline array of strings
  • an inline array of labeled option objects
  • a string ID referencing an options node

flavor

May be:

  • "buttons"
  • "badges"
  • "list"

Default value type

  • string | null

Example

{
id: 'primary_language',
type: 'selectInput',
label: 'Primary Language',
options: 'language_options',
flavor: 'list',
placeholder: 'Select a language...',
}

multiSelectInput

A multi-select input.

Properties

  • id — required
  • type: "multiSelectInput" — required
  • showIf — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • defaultValue — optional
  • flavor — optional
  • options — required
  • placeholder — optional
  • showExplicitNone — optional

options

Must be either:

  • an inline array of strings
  • an inline array of labeled option objects
  • a string ID referencing an options node

flavor

May be:

  • "buttons"
  • "badges"
  • "list"

Default value type

  • string[] | null

Example

{
id: 'symptoms_list',
type: 'multiSelectInput',
label: 'Reported Symptoms',
options: ['Dizziness', 'Nausea', 'Fatigue', 'Shortness of Breath'],
flavor: 'badges',
}

Example with labeled options:

{
id: 'mobility_barriers',
type: 'multiSelectInput',
label: 'Mobility Barriers',
options: [
{ label: 'Pain', value: 'pain' },
{ label: 'Weakness', value: 'weakness' },
{ label: 'Poor Balance', value: 'poor_balance' },
],
flavor: 'list',
}

Option formats

Selection-based inputs (selectInput and multiSelectInput) support three ways to define options.

1. Inline string options

options: ['Left', 'Right', 'Bilateral']

These are interpreted as:

;[
{ label: 'Left', value: 'Left' },
{ label: 'Right', value: 'Right' },
{ label: 'Bilateral', value: 'Bilateral' },
]

2. Inline labeled options

options: [
{ label: 'Yes', value: 'yes' },
{ label: 'No', value: 'no' },
]

Use this when the displayed label should differ from the stored value.

3. Reusable options node reference

options: 'my_shared_options'

This points to an options node elsewhere in the schema.


Explicit “none” behavior

selectInput and multiSelectInput can opt into explicit-none semantics with:

showExplicitNone: true

This lets RehabAlpha distinguish:

  • unanswered
  • explicitly none

Persisted meaning

For selectInput:

  • null = unanswered
  • '' = explicitly none

For multiSelectInput:

  • null = unanswered
  • [] = explicitly none

Form-state meaning

The UI keeps a separate shadow flag for explicit-none state, while the visible control value stays normalized:

  • selectInput form values use strings
  • multiSelectInput form values use arrays

That lets the form stay predictable while preserving the semantic difference between unanswered and explicit none.


Default values

When visible defaults are generated, RehabAlpha uses these fallbacks unless a node provides its own defaultValue.

  • textInput''
  • textAreaInput''
  • dateInput''
  • timeInput''
  • numberInputnull
  • checkboxInputfalse
  • selectInputnull
  • multiSelectInputnull

For selection fields, the form layer later normalizes those values into UI-friendly shapes:

  • selectInput form value → string
  • multiSelectInput form value → string array

Conditional logic

Conditions drive:

  • showIf
  • conditional
  • switch
  • multiSwitch

A condition may be:

  • a single field comparison
  • a grouped condition such as all, any, notAll, or none

Supported condition operators

Equality operators

equals

True when the field value exactly matches the provided value.

{ field: 'has_pain', equals: true }

doesNotEqual

True when the field value does not exactly match the provided value.

{ field: 'pain_type', doesNotEqual: 'Acute' }

Membership operators

isIn

True when a non-array field exactly matches any value in the provided list.

{ field: 'discipline_choice', isIn: ['PT', 'OT'] }

isNotIn

True when a non-array field does not match any value in the provided list.

{ field: 'payor_name', isNotIn: ['Private Pay', 'Other'] }

includes

True when an array field includes the provided value.

{ field: 'symptoms_list', includes: 'Dizziness' }

doesNotInclude

True when an array field does not include the provided value.

{ field: 'symptoms_list', doesNotInclude: 'Nausea' }

Comparison operators

lessThan

{ field: 'pain_score', lessThan: 5 }

lessThanOrEqualTo

{ field: 'pain_score', lessThanOrEqualTo: 5 }

greaterThan

{ field: 'pain_score', greaterThan: 5 }

greaterThanOrEqualTo

{ field: 'pain_score', greaterThanOrEqualTo: 5 }

Group operators

all

Every condition in the array must be true.

{
all: [
{ field: 'has_pain', equals: true },
{ field: 'pain_score', greaterThanOrEqualTo: 7 },
],
}

any

At least one condition in the array must be true.

{
any: [
{ field: 'has_falls', equals: true },
{ field: 'has_dizziness', equals: true },
],
}

notAll

Returns true when not every condition in the array is true.

{
notAll: [
{ field: 'oriented_person', equals: true },
{ field: 'oriented_place', equals: true },
{ field: 'oriented_time', equals: true },
],
}

none

Returns true when none of the conditions in the array are true.

{
none: [
{ field: 'diet_texture', equals: 'Regular' },
{ field: 'liquid_consistency', equals: 'Thin' },
],
}

Condition behavior details

RehabAlpha evaluates conditions with several important rules.

Positive operators fail on missing values

For these operators, undefined causes the condition to fail:

  • equals
  • isIn
  • includes
  • lessThan
  • lessThanOrEqualTo
  • greaterThan
  • greaterThanOrEqualTo

Negative operators usually pass on missing values

For these operators, undefined usually causes the condition to pass:

  • doesNotEqual
  • isNotIn
  • doesNotInclude

Comparison behavior

For comparison operators (lessThan, lessThanOrEqualTo, greaterThan, greaterThanOrEqualTo), RehabAlpha uses strict scalar comparison rules:

  • both values must be scalars
  • both values must be the same type
  • valid comparisons are string-to-string or number-to-number
  • undefined, null, and empty strings do not satisfy positive comparisons

This avoids JavaScript coercion surprises like:

  • 5 < '10'
  • '2' > 10
  • '' < 5

Missing means missing

Render-time condition evaluation preserves missing values as undefined.

Unset fields are not automatically coerced to null before condition evaluation.

That means these behave differently:

{ field: 'x', equals: null }
{ field: 'x', doesNotEqual: null }

An unset field is treated as missing, not as explicitly null.


Special context variables

Conditions can reference not only user-entered fields, but also a small set of special context variables injected by RehabAlpha.

These IDs begin with *.

Supported special variables are:

  • *disciplineId
  • *facilityType
  • *templateType
  • *payorTypes

Meaning

  • *disciplineId — the current discipline
  • *facilityType — the current facility type
  • *templateType — the current document type
  • *payorTypes — the patient’s payor types array

Example:

{
id: 'pt_only_logic',
type: 'conditional',
when: { field: '*disciplineId', equals: 'PT' },
children: ['gait_group'],
}

Example using an array-aware operator:

{
id: 'medicare_logic',
type: 'conditional',
when: { field: '*payorTypes', includes: 'medicarePartA' },
children: ['pdpm_group'],
}

Visible default generation

RehabAlpha does not blindly initialize every input in the schema.

Instead, it generates defaults only for inputs that are currently visible after applying:

  • showIf
  • conditional
  • switch
  • multiSwitch

This process is iterative.

RehabAlpha recomputes visible defaults until the visible set stabilizes or the configured pass limit is reached. This allows defaults and visibility rules to settle into a stable visible result.


Form-state lifecycle

RehabAlpha uses two related representations for custom data.

1. Persisted custom data values

This is the semantic payload stored with the document.

Examples:

  • null means unanswered
  • '' may mean explicit none for eligible single-select inputs
  • [] may mean explicit none for eligible multi-select inputs

2. UI form state

This is the React Hook Form-facing representation used while the clinician is editing.

For selection fields, the form state is normalized into stable control shapes:

  • selectInput form values are strings
  • multiSelectInput form values are arrays

A separate shadow structure tracks explicit-none state.

When the user submits, RehabAlpha combines:

  • the form value
  • the shadow explicit-none flag
  • the node definition

to reconstruct the effective persisted custom-data values.


Pruned saved values

When RehabAlpha prunes custom data values for storage or downstream use, it keeps only values for inputs that are currently visible in the active tree.

A hidden node’s value is not included in the pruned result.

RehabAlpha also excludes values that are effectively empty, including:

  • undefined
  • null
  • empty strings
  • empty arrays

Explicit-none values are preserved.

That means:

  • hidden fields do not leak stale values
  • unanswered fields are not unnecessarily stored
  • explicit user intent is preserved

Validation rules and limits

To keep schemas safe and predictable, RehabAlpha enforces several rules.

Missing references

Schemas cannot reference child IDs that do not exist.

This is checked for:

  • children
  • elseChildren
  • branches[].children
  • fallbackChildren

Missing or invalid options references

Selection inputs cannot reference an options ID that does not exist.

They also cannot reference a node whose type is not options.

Duplicate IDs

Every node ID must be unique across the schema.

Maximum options size

An authored options array may contain at most 256 items.

Options array rules

  • string option arrays cannot contain duplicates
  • labeled option arrays cannot contain duplicate value fields
  • do not mix strings and labeled objects in the same authored options array

Hydration depth limit

During hydration, RehabAlpha enforces a maximum nested depth of MAX_SCHEMA_HYDRATION_DEPTH.

Render depth limit

During rendering, RehabAlpha enforces a maximum rendered depth of MAX_SCHEMA_RENDER_DEPTH.

This is applied to the actually visible rendered tree, not to hidden nodes.

Rendered duplicate input protection

RehabAlpha rejects rendered trees that would produce the same input ID more than once at the same time.

This check is also applied to the actually visible rendered tree.

That means hidden nodes do not trigger duplicate-rendered-input errors.

Important note

This is a runtime rendered-tree validation, not a static whole-schema proof. The exact result depends on the current values and current special variables.


Server-side custom-data verification

When RehabAlpha receives submitted custom data, it validates that payload against the fetched schema.

The server verifies that:

  1. the schema document exists
  2. the selected form root exists
  3. every submitted key belongs to a real input reachable from that selected form root

If a submitted key does not belong to an input inside the selected tree, the request is rejected.

This prevents stale, stray, or malicious custom-data keys from being accepted.


Inline nodes vs ID references

Child-bearing nodes may define child nodes inline, not just by string ID references.

For example:

;[
{
id: 'history_group',
type: 'group',
label: 'History',
children: [
{
id: 'chief_complaint',
type: 'textInput',
label: 'Chief Complaint',
},
{
id: 'pain_logic',
type: 'conditional',
when: { field: 'has_pain', equals: true },
children: [
{
id: 'pain_level',
type: 'numberInput',
label: 'Pain Level',
min: 0,
max: 10,
isInteger: true,
},
],
},
],
},
]

During parsing, RehabAlpha automatically flattens these nested objects into the same internal flat-node format.

Even so, explicit ID references are usually easier to maintain in large schemas.


Practical authoring tips

Prefer reusable option lists

If the same options appear in multiple places, define them once with an options node.

Use labeled options when labels and stored values differ

If you want the UI to show one thing but store another, use labeled options:

{ label: 'Modified Independent', value: 'mod_independent' }

Keep IDs predictable

Consistent naming helps a lot. For example:

  • pain_group
  • pain_level
  • assist_level_options
  • mobility_barriers

Use showIf for node-level visibility

Use showIf when the visibility rule belongs to the node itself.

Use routing nodes when the structure itself changes

Use:

  • conditional for one true branch and one optional false branch
  • switch for first-match routing
  • multiSwitch for all-match routing

Prefer flat authoring for large schemas

Inline nodes are supported, but flat schemas with explicit IDs are usually easier to debug and reuse.


Common mistakes

Broken child references

You referenced a child ID that does not exist.

Example:

children: ['pain_level_inpt']

when the actual node is:

id: 'pain_level_input'

Invalid options references

A selection input points to a missing ID or to a node that is not an options node.

Duplicate IDs

Every node ID must be unique across the schema.

Duplicate option values

Two options in the same array resolve to the same stored value.

Assuming missing is the same as null

Unset fields remain missing during condition evaluation. They are not automatically converted to null.

Assuming showIf filters option availability

showIf on an options node is currently not used to filter option availability.

Oversized schemas

Schemas that exceed configured limits for schema length, option count, hydration depth, render depth, or rendered duplicate inputs are rejected.


Where to go next

Now that you understand the schema system, the next helpful pages are:

  • Writing Your Own Schema
  • Writing Schemas with AI
  • Schema Library