Schemas overview
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 hydrated roots. Top-level reference nodes remain available as hydrated roots so templates can resolve them for conditions, input defaults, and dynamic text, but the reference nodes themselves are never rendered.
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, lists, 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,
}
colSpan
colSpan is an optional layout property for rendered nodes.
It controls how wide the node appears in the custom data form on desktop screens. RehabAlpha renders custom data in a 12-column grid:
- omit
colSpanfor full width - use an integer from
1through12 - mobile screens always render each node full width
colSpan: 6renders half width on desktopcolSpan: 4renders one-third width on desktopcolSpan: 3renders one-quarter width on desktop
colSpan is supported on:
grouplisttext- input nodes
It is not supported on template, options, conditional, switch, or multiSwitch nodes.
Example:
{
id: 'heart_rate',
type: 'numberInput',
label: 'Heart rate',
colSpan: 6,
}
{
id: 'blood_pressure',
type: 'textInput',
label: 'Blood pressure',
colSpan: 6,
}
Shared input properties
All input nodes share these optional properties:
labeltooltipcolSpanisRequiredToSaveisRequiredToSignshowPriorValuesdefaultValuedefaultReference
These properties are supported on:
textInputtextAreaInputdateInputtimeInputnumberInputcheckboxInputselectInputmultiSelectInput
Show values from prior documents
Set showPriorValues: true on an input node when clinicians should be able to review how the same field was documented earlier in the therapy case.
{
id: 'pain_rating',
type: 'numberInput',
label: 'Pain rating',
min: 0,
max: 10,
showPriorValues: true,
}
When this property is present:
- a table icon appears to the right of the input in clinical document create and edit forms
- hovering over or focusing the table icon displays
Document,Date, andValuecolumns - values are collected from prior clinical documents in the same therapy case
- fields match only when their
idvalues are exactly the same - rows with
null,undefined, or an empty string are omitted;false,0, and arrays remain available - rows are sorted from earliest to latest
Clinical documents are ordered by their service date. Progress reports use their end date. When multiple documents share a date, the order is evaluation, treatment, progress report, re-evaluation, then discharge. Treatments on the same date are ordered by start time.
Use the same input node from templates for each applicable clinical document type when you want one continuous history. The property does not show prior-value history on case templates, and historical document versions are not included.
showPriorValues is optional. When provided, its only valid value is true.
Set input defaults
Use defaultValue for a literal starting value. Use defaultReference when the starting value should come from a prior clinical document in the same therapy case. An input may define one of these properties, but not both.
Literal defaults
Use a literal when every new document should start with the same value:
{
id: 'goal_status',
type: 'selectInput',
label: 'Goal status',
options: ['Not started', 'In progress', 'Met', 'Discontinued'],
defaultValue: 'Not started',
}
The literal must match the input's default value type. For example, a checkbox uses a boolean and a number input uses a number or null.
Temporal defaults
A temporal default uses two nodes: a top-level reference that describes the historical lookup, and an input whose defaultReference names that reference node.
{
id: 'prior_goal_status',
type: 'reference',
field: 'goal_status',
documentTypes: ['evaluation', 'treatment', 'progressReport', 'reevaluation', 'discharge'],
select: 'latestNonEmpty',
fallback: 'Not started',
},
{
id: 'goal_status',
type: 'selectInput',
label: 'Goal status',
options: ['Not started', 'In progress', 'Met', 'Discontinued'],
defaultReference: 'prior_goal_status',
}
The reference format is:
{
id: string,
type: 'reference',
field: string,
documentTypes: Array<
'evaluation' | 'treatment' | 'progressReport' | 'reevaluation' | 'discharge'
>,
select: 'first' | 'previous' | 'latestNonEmpty',
fallback?: literalValue,
}
field must identify an existing input node. It cannot identify another reference node. When an input uses the reference as its default, the source and destination must have exactly the same input type. For example, a selectInput may copy another selectInput, but it cannot copy a textInput. Values are never converted between types.
fallback is optional. It must be a valid literal default for the source input. RehabAlpha uses it only when the historical lookup is unresolved. The fallback becomes the reference's effective value for every consumer, including conditions. Define separate reference nodes when consumers need different fallback behavior.
For example, a number input can copy a differently named number input from the evaluation:
{
id: 'evaluation_weight',
type: 'numberInput',
label: 'Evaluation weight',
},
{
id: 'evaluation_weight_reference',
type: 'reference',
field: 'evaluation_weight',
documentTypes: ['evaluation'],
select: 'first',
fallback: null,
},
{
id: 'current_weight',
type: 'numberInput',
label: 'Current weight',
defaultReference: 'evaluation_weight_reference',
}
Every value in documentTypes must be a unique supported clinical document type. RehabAlpha searches the union of the listed types. List all five types to search any prior clinical document. An empty array is valid and matches no documents, so the reference uses its fallback when one is provided.
For a non-empty array, the source input must be reachable from a clinical template that can produce at least one selected document type. References and temporal defaults are not allowed in case templates.
How the selectors choose a document
RehabAlpha applies the documentTypes union first, then considers only strictly prior documents in the same therapy case:
firstselects the earliest matching prior document.previousselects the immediately prior matching document.latestNonEmptysearches matching prior documents from newest to oldest until it finds a compatible value that is not missing,undefined,null, or an empty string.
first and previous inspect only the selected document; they do not fall back to another document when its field is missing or incompatible. When present and compatible, they preserve the selected value exactly, including null, an empty string, an empty array, false, or 0.
latestNonEmpty skips missing, incompatible, undefined, null, and empty-string values. It treats an empty array, false, and 0 as values and may select them.
Clinical documents use the same chronology as prior-value history: service date first, with progress reports using their end date; then evaluation, treatment, progress report, re-evaluation, and discharge on the same date. Treatments on the same date are ordered by start time. The currently saved document is excluded, and a new unsaved draft sorts after saved documents with the same chronology key.
When RehabAlpha initializes an input, an existing or directly supplied field value wins. Next comes the effective value of its defaultReference, then its literal defaultValue, then the input type's intrinsic default. Because defaultReference and defaultValue are mutually exclusive, only one authored default can apply. An exact first or previous result such as null, an empty string, an empty array, false, or 0 counts as resolved and therefore wins over the reference's fallback.
Live defaults and saved snapshots
On a new unsaved form, or after selecting a replacement template, RehabAlpha refreshes temporal lookups when the available prior documents or their ordering changes.
An input using defaultReference remains refreshable only until that input is directly edited. This includes a displayed reference fallback. Editing includes entering or clearing a value, choosing explicit none, or applying an AI-generated value. After that interaction, changes in prior documents do not overwrite the clinician's value.
Freezing an input does not freeze its reference. The reference continues refreshing while the form is unsaved so conditions and other inputs that share it still see the current historical result.
Saving snapshots the input values in the document's custom values and snapshots every temporal reference node used by the template, including references used only by input defaults or dynamic text. A temporal reference named by dynamic text is still resolved and snapshotted when its text node is hidden. Live input segments use the ordinary saved custom input values and add no separate snapshot data. Reopening the same saved template uses those saved values and temporal snapshots instead of recalculating history. Later edits or deletions of a source document do not cascade into the saved document. Selecting a replacement template resolves a new set of snapshots.
Use prior values in conditions
A reference node does not render by itself. Its ID can be used as the field in showIf, conditional, switch, or multiSwitch conditions. The same reference may also supply an input's defaultReference or a dynamic text segment.
{
id: 'prior_goal_status',
type: 'reference',
field: 'goal_status',
documentTypes: ['evaluation', 'treatment', 'progressReport', 'reevaluation', 'discharge'],
select: 'previous',
fallback: 'Not started',
}
{
id: 'goal_status',
type: 'selectInput',
label: 'Goal status',
options: ['Not started', 'In progress', 'Met', 'Discontinued'],
defaultReference: 'prior_goal_status',
}
{
id: 'prior_goal_was_met',
type: 'conditional',
when: { field: 'prior_goal_status', equals: 'Met' },
children: ['review_met_goal_text'],
}
Reference nodes must remain top-level schema definitions. Do not place them in children, elseChildren, branches[].children, or fallbackChildren. They are hidden, are never rendered as form controls, and cannot be consumed by case templates.
While a new form is unsaved, reference values refresh whenever the prior-document snapshot changes. Saving stores every effective reference value with the document; reopening that saved template does not resolve them again. If the source changes between loading and saving, RehabAlpha asks the clinician to reload rather than silently changing the snapshot or conditional layout.
When a reference is unresolved and has no fallback, conditions see the value as missing (undefined). The normal missing-value condition rules still apply: positive operators fail, while negative operators usually pass.
Supported node types
Schemas currently support these node types:
Structure and logic nodes
grouplistconditionalswitchmultiSwitch
Data and metadata nodes
textoptionsreference
Input nodes
textInputtextAreaInputdateInputtimeInputnumberInputcheckboxInputselectInputmultiSelectInput
Structure and logic nodes
group
A visual container used to organize related content.
Properties
id— requiredtype: "group"— requiredshowIf— optionalcolSpan— 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'],
}
list
A presentational ordered or unordered list of authored text. Use a list when each item should appear only while its own condition is true, such as a summary of the goals selected for an evaluation.
Properties
id— requiredtype: "list"— requiredshowIf— optionalcolSpan— optionalordered— optional boolean; defaults tofalsechildren— optional array containing onlytextorlistnodes
Rendering behavior
ordered: truerenders an<ol>;falseor omitted renders a<ul>.- every visible direct child renders as one
<li> - a text child renders its escaped static or reference-backed text inside the list item
- a nested list renders its own
<ul>or<ol>inside its parent<li> showIfcan hide the whole list, an individual text item, or a nested list- hidden children do not leave empty list items
- a nested list with no visible descendants is omitted, including its parent list item
- a list with no visible items is omitted
colSpancontrols the outer list's placement in the 12-column form grid; childcolSpanvalues do not change list-item layout
children may use node IDs or inline child objects, but every resolved direct child must have type text or list. Input, group, routing, options, reference, and template nodes are not valid list children. Missing IDs, unresolved references, and literal null children are also rejected.
List nodes are static and non-repeatable. They do not iterate over an input collection or
automatically create items for goals added while the form is open. A text item may reference live
inputs from the same template or prior-document reference nodes. To summarize authored goal
statements, define one text child per possible goal and use each child's showIf to decide whether
it appears.
List nodes render only in custom-data create and edit forms. They do not add structural content to saved detail pages, generated PDFs, or signed output, and the list itself stores no custom-data value.
Example
{
id: 'evaluation_goals',
type: 'list',
ordered: false,
colSpan: 12,
children: [
{
id: 'walking_goal_summary',
type: 'text',
value: 'Improve household walking independence.',
showIf: { field: 'create_walking_goal', equals: true },
},
{
id: 'stairs_goal_summary',
type: 'text',
value: 'Negotiate stairs with a handrail.',
showIf: { field: 'create_stairs_goal', equals: true },
},
{
id: 'advanced_goals',
type: 'list',
ordered: true,
showIf: { field: 'include_advanced_goals', equals: true },
children: [
{
id: 'community_walking_goal_summary',
type: 'text',
value: 'Walk safely in the community.',
},
],
},
],
}
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: '*placeOfServiceId', equals: '11' },
children: ['office_group'],
},
{
when: { field: '*placeOfServiceId', equals: '12' },
children: ['home_group'],
},
],
}
Data and metadata nodes
text
Displays static text or read-only formatted values from the current form or prior clinical documents.
Properties
id— requiredtype: "text"— requiredshowIf— optionalcolSpan— optionalvalue— required static string or dynamic segment array
Static text
{
id: 'gait_note',
type: 'text',
value: 'Assess gait using the least restrictive assistive device possible.',
}
Dynamic text
Use a dynamic segment array when clinicians should see values from the current form or a prior document without adding another editable control. Each segment is either a literal string or a reference object:
type TextReferenceSegment = {
reference: string
missingText?: string
}
type TextSegment = string | TextReferenceSegment
type TextValue = string | [TextSegment, ...TextSegment[]]
Dynamic text follows these rules:
- the array must contain between 1 and 64 segments
- at least one segment must be a reference object
- every
referencemust identify an existing input node or top-level temporalreferencenode - a referenced input must be reachable from every template that can render the dynamic text
- a reference object may contain only
referenceand optionalmissingText - the same reference may appear more than once
- literal strings and all
missingTextstrings share the text node's 1,024-character authored-text limit - built-in form field paths, special variables, expressions, template literals, Python-style f-strings, HTML, and Markdown are not supported as dynamic references
- case templates may use live input references, but they cannot use temporal
referencenodes
RehabAlpha concatenates the segments and formats each value according to its source input:
- a reference with no available value displays its
missingText, or—whenmissingTextis omitted - a resolved reference fallback is formatted like any other resolved value
nulland an empty array display asNone- an empty string displays as
— - booleans display as
YesorNo - numbers use their unmodified string form
- valid date values display as
M/d/yyyy, such as8/15/2026 - valid time values display as
h:mm a, such as1:30 PM - select and multi-select values display their authored option labels, falling back to the raw stored values when no matching label exists
- array values are separated with commas
- other strings display as written
Literal and resolved content is rendered as escaped plain text. Schema content is never evaluated, and HTML or Markdown is not rendered as markup.
Dynamic text appears only in create and edit forms. It does not add content to saved detail pages, generated PDFs, or signed output. A live input segment updates immediately as the clinician edits that input. When editing a saved document, it starts with the rehydrated saved input value. Temporal reference segments refresh from available prior documents on an unsaved clinical form and use the stored reference snapshots when editing a saved document.
Summarize goals in the current form
The following example shows a summary list near the bottom of an evaluation. The list item appears when the clinician chooses to create the walking goal, and its text updates as the goal fields change:
[
{
id: 'evaluation_template',
type: 'template',
label: 'Evaluation',
appliesTo: 'evaluation',
priority: 1,
children: [
'make_walking_goal',
'walking_goal_description',
'walking_goal_value',
'walking_goal_date',
'goal_summary',
],
},
{
id: 'make_walking_goal',
type: 'checkboxInput',
label: 'Create a walking goal',
},
{
id: 'walking_goal_description',
type: 'textInput',
label: 'Walking goal',
showIf: { field: 'make_walking_goal', equals: true },
},
{
id: 'walking_goal_value',
type: 'numberInput',
label: 'Target distance in feet',
showIf: { field: 'make_walking_goal', equals: true },
},
{
id: 'walking_goal_date',
type: 'dateInput',
label: 'Target date',
showIf: { field: 'make_walking_goal', equals: true },
},
{
id: 'goal_summary',
type: 'list',
children: [
{
id: 'walking_goal_summary',
type: 'text',
showIf: { field: 'make_walking_goal', equals: true },
value: [
{ reference: 'walking_goal_description' },
' · Target ',
{ reference: 'walking_goal_value' },
' by ',
{ reference: 'walking_goal_date' },
],
},
],
},
]
For several fixed, schema-defined goals, add one conditional text child per goal. When no child is visible, the list does not render an empty container. This does not create a repeatable collection; the inputs and summary entries must still be authored in the schema.
Show values from prior documents
The following example lets a clinician enter a goal's target date and target value on an evaluation, then see both values as read-only text on a later treatment:
[
{
id: 'evaluation_template',
type: 'template',
label: 'Evaluation',
appliesTo: 'evaluation',
priority: 1,
children: ['goal_target_date', 'goal_target_value'],
},
{
id: 'treatment_template',
type: 'template',
label: 'Treatment',
appliesTo: 'treatment',
priority: 1,
children: ['goal_target_summary'],
},
{
id: 'goal_target_date',
type: 'dateInput',
label: 'Goal target date',
},
{
id: 'goal_target_value',
type: 'numberInput',
label: 'Goal target value',
},
{
id: 'prior_goal_target_date',
type: 'reference',
field: 'goal_target_date',
documentTypes: ['evaluation'],
select: 'first',
},
{
id: 'prior_goal_target_value',
type: 'reference',
field: 'goal_target_value',
documentTypes: ['evaluation'],
select: 'first',
},
{
id: 'goal_target_summary',
type: 'text',
value: [
'Evaluation goal: ',
{ reference: 'prior_goal_target_value', missingText: 'No target value' },
' by ',
{ reference: 'prior_goal_target_date', missingText: 'no target date' },
'.',
],
},
]
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
reference
Resolves and snapshots a value from a prior clinical document so conditions, input defaults, and dynamic text can share it without rendering another input.
Properties
id— requiredtype: "reference"— requiredfield— required source input IDdocumentTypes— required array of unique clinical document types; an empty array matches noneselect— required selector:"first","previous", or"latestNonEmpty"fallback— optional literal used when the historical lookup is unresolved
Notes
referencenodes must be top-level schema definitions and cannot appear in child lists.fieldmust identify an existing input node and cannot identify anotherreference.fallbackmust satisfy the source input's literal-default rules.referencenodes do not render and are not part of ordinary custom input values.- conditions may use the reference ID as their
field, inputs of the same type as the source may name it indefaultReference, and dynamic text may name it in a reference segment. - a dynamic text segment may also name a reachable input directly; doing so reads the current form value and does not make the input a temporal reference.
- case templates may use live input segments, but they cannot use temporal references.
Example
{
id: 'previous_pain_rating',
type: 'reference',
field: 'pain_rating',
documentTypes: ['treatment'],
select: 'previous',
fallback: null,
}
Input nodes
textInput
A single-line text field.
Properties
id— requiredtype: "textInput"— requiredshowIf— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueminLength— 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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueplaceholder— 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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValuemin— 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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueplaceholder— 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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValuemin— optionalmax— optionalstep— optionalisInteger— optionalplaceholder— optional
Notes
stepmay be a number greater than zero or"any"- a literal
defaultValueor referencefallbackmay 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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueicon— optional
Default value type
boolean
icon
Use icon when you want the checkbox to appear as an icon toggle instead of a standard checkbox control.
The stored value is still a boolean.
Supported values:
accessibilitycalendarchartNoAxesCombinedcirclecircleAlertcircleArrowDowncircleArrowLeftcircleArrowRightcircleArrowUpcircleArrowOutDownLeftcircleArrowOutDownRightcircleArrowOutUpLeftcircleArrowOutUpRightcircleCheckcircleCheckBigcircleChevronDowncircleChevronLeftcircleChevronRightcircleChevronUpcircleDashedcircleDividecircleDollarSigncircleDotcircleDotDashedcircleEllipsiscircleEqualcircleFadingArrowUpcircleFadingPluscircleGaugecircleMinuscircleOffcircleParkingcircleParkingOffcirclePausecirclePercentcirclePilecirclePlaycirclePluscirclePoundSterlingcirclePowercircleQuestionMarkcircleSlashcircleSlash2circleSmallcircleStarcircleStopcircleUsercircleUserRoundcircleXclipboardListclockheartlocklockOpennotebookPenstartargetthumbsDownthumbsUptimertrendingDowntrendingUp
Example
{
id: 'patient_refused',
type: 'checkboxInput',
label: 'Patient refused treatment today',
defaultValue: false,
}
Icon example
{
id: 'make_money_management_goal',
type: 'checkboxInput',
label: 'Make this a goal?',
icon: 'target',
}
selectInput
A single-select input.
Properties
id— requiredtype: "selectInput"— requiredshowIf— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueflavor— 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
An authored literal defaultValue or reference fallback cannot be ''. Use null or omit it instead. Historical reference values may still resolve to an empty string.
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— optionalcolSpan— optionallabel— optionaltooltip— optionalisRequiredToSave— optionalisRequiredToSign— optionalshowPriorValues— optionaldefaultValue— optional literaldefaultReference— optional reference node ID; cannot be combined withdefaultValueflavor— 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
An authored literal defaultValue or reference fallback cannot be []. Use null or omit it instead. Historical reference values may still resolve to an empty array.
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 an input has no persisted value and neither defaultReference nor defaultValue supplies one, RehabAlpha uses these intrinsic defaults.
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*payorId*payorType*paymentModelType*placeOfServiceId*templateType
Meaning
*disciplineId— the current discipline*payorId— the ID of the primary payor snapshot on the matched billing episode*payorType— the payor type in that primary payor snapshot*paymentModelType— the payment model type in that primary payor snapshot*placeOfServiceId— the current Place of Service code*templateType— the current document type
The payor variables are resolved together from the billing episode that matches the current discipline and effective date. Evaluations, re-evaluations, and discharges use their document date. Treatments and progress reports use their start date. Therapy cases use the case start date and do not resolve a payor until a discipline is selected.
If no billing episode matches, all three payor variables are null. They also remain null while a
therapy case is missing its discipline or start date. Otherwise, the values come from the billing
episode's assignment-time snapshot. Later edits to or deletion of the main payor record do not
change the values for that existing billing episode.
Valid *payorType values are:
Commercial InsuranceManaged Care Part AManaged Care Part BMedicaidMedicare Part AMedicare Part BPrivate PayWorkers' Compensation
Valid *paymentModelType values are customFeeSchedule, durationBasedPayment,
medicarePartBMPFS, and pdpmCaseMixPerDiem.
Example:
{
id: 'pt_only_logic',
type: 'conditional',
when: { field: '*disciplineId', equals: 'PT' },
children: ['gait_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
Dynamic text segments have an additional check: each value[].reference must identify either
an existing input reachable from every template that can render the text or a top-level temporal
reference node. Options nodes, special variables, built-in form paths, and other node types are
not valid there.
For a list, every resolved children entry must identify a text or another list node. Other node types and literal null children are rejected.
Dynamic text limits
A dynamic text value must contain between 1 and 64 strict segments and at least one reference segment. Across the whole array, literal text and missingText content may total no more than 1,024 authored characters.
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