Schemas
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:
idtype
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:
childrenelseChildrenbranches[].childrenfallbackChildrenoptions
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:
- finds the templates whose
appliesTomatches the current document type - chooses the one with the highest numeric
priority
Important details:
- templates without a numeric
priorityare 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 showIfdoes 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:
childrenelseChildrenbranches[].childrenfallbackChildren
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
optionsreferences are resolved and normalized- circular references are rejected
- missing node references are rejected
- missing
optionsreferences are rejected - non-
optionsreferences insideoptionsare 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:
showIfapplies to visible nodes such as groups, logic nodes, text nodes, options nodes, and input nodesshowIfdoes not apply to top-level form-definition objectsshowIfon anoptionsnode 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:
labeltooltipisRequiredToSaveisRequiredToSigndefaultValue
These properties are supported on:
textInputtextAreaInputdateInputtimeInputnumberInputcheckboxInputselectInputmultiSelectInput
Supported node types
Schemas currently support these node types:
Structure and logic nodes
groupconditionalswitchmultiSwitch
Data and metadata nodes
textoptions
Input nodes
textInputtextAreaInputdateInputtimeInputnumberInputcheckboxInputselectInputmultiSelectInput
Structure and logic nodes
group
A visual container used to organize related content.
Properties
id— requiredtype: "group"— requiredshowIf— optionallabel— optionaldescription— optionalchildren— optional
Notes
childrenmay contain string node IDschildrenmay also contain inline child objects during authoring- a hydrated
groupexposeschildrenas 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— requiredtype: "conditional"— requiredshowIf— optionalwhen— requiredchildren— optionalelseChildren— optional
Notes
childrenmay contain string node IDs or inline child objectselseChildrenmay contain string node IDs or inline child objects- a hydrated
conditionalexposes 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— requiredtype: "switch"— requiredshowIf— optionalbranches— requiredfallbackChildren— optional
Each branch contains:
when— requiredchildren— optional
Notes
branches[].childrenmay contain string node IDs or inline child objectsfallbackChildrenmay contain string node IDs or inline child objects- a hydrated
switchexposes 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— requiredtype: "multiSwitch"— requiredshowIf— optionalbranches— required
Each branch contains:
when— requiredchildren— optional
Notes
branches[].childrenmay contain string node IDs or inline child objects- a hydrated
multiSwitchexposes 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— requiredtype: "text"— requiredshowIf— optionalvalue— 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— requiredtype: "options"— requiredshowIf— optionalitems— 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 thelabel - within one authored options array, do not mix strings and labeled objects
- string options cannot be empty
- labeled option
labelvalues cannot be empty - labeled option
valuevalues cannot be empty - duplicate option values are not allowed
- hydrated options are normalized into
{ label, value }objects showIfon anoptionsnode is currently not used to filter option availability
Input nodes
textInput
A single-line text field.
Properties
id— requiredtype: "textInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalminLength— optionalmaxLength— optionalpattern— optionalpatternMessage— optionalplaceholder— 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— requiredtype: "textAreaInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalplaceholder— 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— requiredtype: "dateInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalmin— optionalmax— optionalplaceholder— 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— requiredtype: "timeInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalplaceholder— optional
Default value type
string
Example
{
id: 'time_of_assessment',
type: 'timeInput',
label: 'Time of Assessment',
}
numberInput
A numeric input field.
Properties
id— requiredtype: "numberInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalmin— optionalmax— optionalstep— optionalisInteger— optionalplaceholder— optional
Notes
stepmay be a number or"any"defaultValuemay be a number ornull
Example
{
id: 'oxygen_sat',
type: 'numberInput',
label: 'O2 Saturation (%)',
min: 0,
max: 100,
isInteger: true,
}
checkboxInput
A boolean input.
Properties
id— requiredtype: "checkboxInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— 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— requiredtype: "selectInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalflavor— optionaloptions— requiredplaceholder— optionalshowExplicitNone— optional
options
Must be either:
- an inline array of strings
- an inline array of labeled option objects
- a string ID referencing an
optionsnode
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— requiredtype: "multiSelectInput"— requiredshowIf— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionaldefaultValue— optionalflavor— optionaloptions— requiredplaceholder— optionalshowExplicitNone— optional
options
Must be either:
- an inline array of strings
- an inline array of labeled option objects
- a string ID referencing an
optionsnode
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:
selectInputform values use stringsmultiSelectInputform 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→''numberInput→nullcheckboxInput→falseselectInput→nullmultiSelectInput→null
For selection fields, the form layer later normalizes those values into UI-friendly shapes:
selectInputform value → stringmultiSelectInputform value → string array
Conditional logic
Conditions drive:
showIfconditionalswitchmultiSwitch
A condition may be:
- a single field comparison
- a grouped condition such as
all,any,notAll, ornone
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:
equalsisInincludeslessThanlessThanOrEqualTogreaterThangreaterThanOrEqualTo
Negative operators usually pass on missing values
For these operators, undefined usually causes the condition to pass:
doesNotEqualisNotIndoesNotInclude
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:
showIfconditionalswitchmultiSwitch
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:
nullmeans 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:
selectInputform values are stringsmultiSelectInputform 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:
undefinednull- 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:
childrenelseChildrenbranches[].childrenfallbackChildren
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
valuefields - 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:
- the schema document exists
- the selected form root exists
- 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_grouppain_levelassist_level_optionsmobility_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:
conditionalfor one true branch and one optional false branchswitchfor first-match routingmultiSwitchfor 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