Skip to main content

Schemas overview

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 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:

  • showIf applies to visible nodes such as groups, lists, 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,
}

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 colSpan for full width
  • use an integer from 1 through 12
  • mobile screens always render each node full width
  • colSpan: 6 renders half width on desktop
  • colSpan: 4 renders one-third width on desktop
  • colSpan: 3 renders one-quarter width on desktop

colSpan is supported on:

  • group
  • list
  • text
  • 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:

  • label
  • tooltip
  • colSpan
  • isRequiredToSave
  • isRequiredToSign
  • showPriorValues
  • defaultValue
  • defaultReference

These properties are supported on:

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

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, and Value columns
  • values are collected from prior clinical documents in the same therapy case
  • fields match only when their id values 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:

  • first selects the earliest matching prior document.
  • previous selects the immediately prior matching document.
  • latestNonEmpty searches 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

  • group
  • list
  • conditional
  • switch
  • multiSwitch

Data and metadata nodes

  • text
  • options
  • reference

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
  • colSpan — 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'],
}

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 — required
  • type: "list" — required
  • showIf — optional
  • colSpan — optional
  • ordered — optional boolean; defaults to false
  • children — optional array containing only text or list nodes

Rendering behavior

  • ordered: true renders an <ol>; false or 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>
  • showIf can 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
  • colSpan controls the outer list's placement in the 12-column form grid; child colSpan values 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 — 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: '*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 — required
  • type: "text" — required
  • showIf — optional
  • colSpan — optional
  • value — 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 reference must identify an existing input node or top-level temporal reference node
  • a referenced input must be reachable from every template that can render the dynamic text
  • a reference object may contain only reference and optional missingText
  • the same reference may appear more than once
  • literal strings and all missingText strings 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 reference nodes

RehabAlpha concatenates the segments and formats each value according to its source input:

  • a reference with no available value displays its missingText, or when missingText is omitted
  • a resolved reference fallback is formatted like any other resolved value
  • null and an empty array display as None
  • an empty string displays as
  • booleans display as Yes or No
  • numbers use their unmodified string form
  • valid date values display as M/d/yyyy, such as 8/15/2026
  • valid time values display as h:mm a, such as 1: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 — 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

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 — required
  • type: "reference" — required
  • field — required source input ID
  • documentTypes — required array of unique clinical document types; an empty array matches none
  • select — required selector: "first", "previous", or "latestNonEmpty"
  • fallback — optional literal used when the historical lookup is unresolved

Notes

  • reference nodes must be top-level schema definitions and cannot appear in child lists.
  • field must identify an existing input node and cannot identify another reference.
  • fallback must satisfy the source input's literal-default rules.
  • reference nodes 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 in defaultReference, 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 — required
  • type: "textInput" — required
  • showIf — optional
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • min — optional
  • max — optional
  • step — optional
  • isInteger — optional
  • placeholder — optional

Notes

  • step may be a number greater than zero or "any"
  • a literal defaultValue or reference fallback 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
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • icon — 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:

  • accessibility
  • calendar
  • chartNoAxesCombined
  • circle
  • circleAlert
  • circleArrowDown
  • circleArrowLeft
  • circleArrowRight
  • circleArrowUp
  • circleArrowOutDownLeft
  • circleArrowOutDownRight
  • circleArrowOutUpLeft
  • circleArrowOutUpRight
  • circleCheck
  • circleCheckBig
  • circleChevronDown
  • circleChevronLeft
  • circleChevronRight
  • circleChevronUp
  • circleDashed
  • circleDivide
  • circleDollarSign
  • circleDot
  • circleDotDashed
  • circleEllipsis
  • circleEqual
  • circleFadingArrowUp
  • circleFadingPlus
  • circleGauge
  • circleMinus
  • circleOff
  • circleParking
  • circleParkingOff
  • circlePause
  • circlePercent
  • circlePile
  • circlePlay
  • circlePlus
  • circlePoundSterling
  • circlePower
  • circleQuestionMark
  • circleSlash
  • circleSlash2
  • circleSmall
  • circleStar
  • circleStop
  • circleUser
  • circleUserRound
  • circleX
  • clipboardList
  • clock
  • heart
  • lock
  • lockOpen
  • notebookPen
  • star
  • target
  • thumbsDown
  • thumbsUp
  • timer
  • trendingDown
  • trendingUp

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 — required
  • type: "selectInput" — required
  • showIf — optional
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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

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 — required
  • type: "multiSelectInput" — required
  • showIf — optional
  • colSpan — optional
  • label — optional
  • tooltip — optional
  • isRequiredToSave — optional
  • isRequiredToSign — optional
  • showPriorValues — optional
  • defaultValue — optional literal
  • defaultReference — optional reference node ID; cannot be combined with defaultValue
  • 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

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:

  • 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 an input has no persisted value and neither defaultReference nor defaultValue supplies one, RehabAlpha uses these intrinsic defaults.

  • 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
  • *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 Insurance
  • Managed Care Part A
  • Managed Care Part B
  • Medicaid
  • Medicare Part A
  • Medicare Part B
  • Private Pay
  • Workers' 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:

  • 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

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 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