Skip to main content

Schema Format

eSheet's FormDefinition model is JSON-shaped and identified by the id field. YAML is the canonical on-disk representation for committed form layouts, while JSON is the wire/API representation. Both formats parse to the same model and are accepted by the builder and renderer. This page documents the complete structure.

Which format should I commit?

Commit form layouts as *.esheet.yaml. YAML keeps diffs reviewable, supports comments, and preserves readable multi-line strings. The builder's Export action uses YAML by default.

Use *.esheet.json when a downstream system requires JSON, or when sending a definition over an API. Existing .esheet.json layouts remain supported indefinitely; no migration is required.

Form Definition

The top-level object that describes an entire form:

interface FormDefinition {
/** Unique form identifier */
id: string;
/** Optional form title */
title?: string;
/** Optional form description */
description?: string;
/** When true, enables dangerously embedded JS for calculations and conditions */
dangerouslyAllowJS?: boolean;
/** Pages — every form has at least one page; fields live inside pages */
pages: PageEntry[];
}

interface PageEntry {
/** Unique page identifier */
id: string;
/** Optional display title (shown as tab label in the builder) */
title?: string;
/**
* Reserved: automatically advance to the next page when all fields are answered.
* Not yet active in the renderer.
*/
autoAdvance?: boolean;
/** Fields rendered on this page */
fields?: FieldDefinition[];
}

A form with a single page renders as a normal single-scroll form — no navigation UI is shown. A form with multiple pages shows a Previous / N of total / Next navigation bar in the renderer. See Pages for full details.

Example

id: patient-intake-form
title: Patient Intake Form
description: Please fill out all required fields
pages:
- id: page_1
title: Demographics
fields:
# Collect identity information before asking health questions.
- id: name
fieldType: text
question: Full Name
required: true
inputType: string
- id: dob
fieldType: text
question: Date of Birth
inputType: date

Field Definition

Every field in a form is described by a FieldDefinition. This is a "wide" schema -- not every property applies to every field type.

interface FieldDefinition {
/** Unique identifier within the form */
id: string;
/** Determines rendering and behavior */
fieldType: FieldType;
/** The question/label shown to the user */
question?: string;
/** Whether a response is required */
required?: boolean;
/** Conditional rules (see Conditional Logic) */
rules?: ConditionalRule[];
/** Width occupied by the field in the layout grid */
width?: FieldWidth;

// --- Text fields ---
/** Input type variant for text fields */
inputType?: TextInputType;
/** Unit suffix displayed after the input (e.g. "kg", "cm") */
unit?: string;

// --- Choice fields ---
/** Options for radio, check, dropdown, multiselect, multitext, rating, ranking */
options?: FieldOption[];
/** Layout direction for fields that display options */
optionLayout?: OptionLayout;

// --- Matrix fields ---
/** Rows for singlematrix and multimatrix */
rows?: MatrixRow[];
/** Columns for singlematrix and multimatrix */
columns?: MatrixColumn[];

// --- Rich content ---
/** Raw HTML content for html fields */
htmlContent?: string;
/** Image URI for image fields */
imageUri?: string;
/** Alt text for image fields */
altText?: string;
/** Caption for image fields */
caption?: string;
/** Placeholder text on the drawing canvas (signature/diagram) */
padPlaceholder?: string;
/** Markdown-like content for display fields (supports expression interpolation) */
content?: string;

// --- Section (container) ---
/** Section title */
title?: string;
/** Nested child fields */
fields?: FieldDefinition[];
}

Field Layout

FieldWidth

The optional width property controls how much of a row a field occupies:

ValueDescription
fullUses the full row width
halfUses half of the row
thirdUses one third of the row

Text, selection, and rating fields default to third. Rich, matrix, and organization fields default to full.

type FieldWidth = 'full' | 'half' | 'third';

OptionLayout

The optional optionLayout property controls how a field's options are arranged:

ValueDescription
stackDisplays one option per line
wrapDisplays options horizontally and wraps as required

Fields that support optionLayout default to wrap.

type OptionLayout = 'stack' | 'wrap';

Field Types

The 19 built-in field types:

Field TypeCategoryAnswer TypeHas OptionsHas MatrixDescription
texttexttextNoNoSingle-line text input with variants
longtexttexttextNoNoMulti-line textarea
multitexttextmultitextYesNoOne text input per option
radioselectionselectionYesNoSingle-select radio buttons
checkselectionmultiselectionYesNoMulti-select checkboxes
booleanselectionselectionNoNoYes/No toggle
dropdownselectionselectionYesNoSingle-select dropdown
multiselectdropdownselectionmultiselectionYesNoMulti-select dropdown
ratingratingselectionYesNoNumeric scale (1-5, 1-10)
rankingratingmultiselectionYesNoDrag-to-order items
sliderratingselectionYesNoRange slider
singlematrixmatrixmatrixNoYesOne selection per row
multimatrixmatrixmatrixNoYesMultiple selections per row
imagerichdisplayNoNoImage display
htmlrichdisplayNoNoRaw HTML embed
signaturerichmediaNoNoDrawing pad for signatures
diagramrichmediaNoNoDrawing pad for markup
displayrichdisplayNoNoMarkdown + expression content
sectionorganizationcontainerNoNoContainer for nested fields

Text Input Types

The text field supports these input type variants via the inputType property:

Input TypeHTML TypeDescription
stringtextPlain text (default)
numbernumberNumeric input
emailemailEmail address
teltelPhone number (auto-formatted)
datedateDate picker
datetime-localdatetime-localDate and time picker
monthmonthMonth picker
timetimeTime picker
urlurlURL input

Options

Choice fields use FieldOption objects:

interface FieldOption {
/** Unique option identifier */
id: string;
/** Display value */
value: string;
/** Optional tooltip or auxiliary text */
text?: string;
/** Optional numeric score for scored surveys (e.g. PHQ-9, GAD-7). When present on
* any option, the field's answer value is the sum of scores for all selected options. */
score?: number;
/** Conditional visibility rules for this option. Only the `visible` effect applies. */
rules?: ConditionalRule[];
}

Matrix Dimensions

Matrix fields use rows and columns:

interface MatrixRow {
id: string;
value: string; // Row label
}

interface MatrixColumn {
id: string;
value: string; // Column header
/** Optional numeric score for this column. Overrides auto-scoring when `scored` is enabled. */
score?: number;
}

Form Response

The response for a submitted form:

/** Maps field IDs to their response values */
type FormResponse = Record<string, FieldResponse>;

interface FieldResponse {
/** Text answer (text, longtext) */
answer?: string;
/**
* Selected option(s):
* - SelectedOption for single-select (radio, dropdown, boolean, rating, slider)
* - SelectedOption[] for multi-select (check, multiselectdropdown, ranking)
* - Record<string, SelectedOption | SelectedOption[]> for matrix (rowId -> column)
*/
selected?:
| SelectedOption
| SelectedOption[]
| Record<string, SelectedOption | SelectedOption[]>;
/** Per-option text for multitext fields */
multitextAnswers?: Record<string, string>;
/** Serialized stroke data (signature) */
signatureData?: string;
/** Base64 PNG image (signature) */
signatureImage?: string;
/** Serialized stroke data (diagram) */
markupData?: string;
/** Base64 PNG image (diagram) */
markupImage?: string;
}

interface SelectedOption {
readonly id: string;
value: string;
}

Example Response

Responses are wire/API payloads, so this example is shown in JSON. Form definitions above use YAML as the canonical committed format.

{
"name": { "answer": "Jane Doe" },
"email": { "answer": "jane@example.com" },
"reason": { "selected": { "id": "opt2", "value": "Follow-up" } },
"symptoms": {
"selected": [
{ "id": "s1", "value": "Headache" },
{ "id": "s3", "value": "Fatigue" }
]
},
"pain_matrix": {
"selected": {
"row_head": { "id": "col_mild", "value": "Mild" },
"row_back": { "id": "col_severe", "value": "Severe" }
}
},
"signature_field": {
"signatureData": "[{\"points\":[...]}]",
"signatureImage": "data:image/png;base64,..."
}
}