List Schema DSL

Every list has a schema: a small domain-specific language (DSL) that describes the list's columns (their keys, types, labels, validation, and conditional visibility). This page is the complete reference for that schema object.

The schema is a JSON object (not a comma-separated string). You send it as the schema field when you:

  • create a list: POST /api/lists (see Lists), or
  • rebuild a list's schema: PUT /api/lists/:id/schema (destructive; wipes and recreates all columns).

For small, non-destructive column edits (rename a label, add/remove one column while preserving row data), send a properties array to PUT /api/lists/:id/schema instead (see Lists → Updating the schema). This page covers the DSL object form.

The schema object

{
  "name": "Books to Read",
  "description": "My reading backlog.",
  "fields": [
    { "key": "title",  "type": "text",    "label": "Title",  "required": true },
    { "key": "author", "type": "text",    "label": "Author" },
    { "key": "year",   "type": "number",  "label": "Year" },
    { "key": "read",   "type": "boolean", "label": "Read",   "defaultValue": false }
  ]
}
KeyRequiredDescription
nameYesSchema name (non-empty string). Required by the validator even when creating a list, where the list's displayed title comes from the top-level title; set both to the same value. On PUT …/schema (rebuild), name becomes the list's new title.
descriptionNoOptional description of the schema.
fieldsYesArray of column definitions. Must contain at least one column (see below).

Every list has at least one column

The fields array can never be empty: every list must define at least one column. An empty schema is rejected:

{ "error": "Invalid schema: DSL must have at least one field", "code": "bad_request" }

This is separate from a column's own required flag: fields must contain ≥ 1 column, while each individual column may independently be optional or required: true for row entry. Column keys must be unique within the schema (duplicates return 400 Invalid schema: Duplicate field keys found: …).

Column (field) definitions

Each entry in fields describes one column. key, type, and label are always required; everything else is optional.

PropertyRequiredDescription
keyYesMachine key stored in each row's rowData (e.g. author). Unique within the schema.
typeYesThe column's data type: one of the Field types listed below.
labelYesHuman-readable column header shown in forms and tables.
requiredNotrue makes the column mandatory when a row is added or edited (default false).
defaultValueNoValue pre-filled for new rows.
optionsselect / multiselectArray of allowed values. Required for select and multiselect.
placeholderNoPlaceholder text for the input.
helpTextNoHelp/tooltip text shown beneath the field.
validationNoExtra rules (see Validation rules below): min, max, minLength, maxLength, pattern, step.
visibleNofalse hides the column by default (default true).
visibilityNoConditional-visibility rule (see Conditional visibility below): show this column only when another column matches a condition.
displayOrderNoInteger ordering; defaults to the field's position in the array.

Each fields[] entry is stored as a ListProperty row (keypropertyKey, labelpropertyName, typepropertyType). Row data (POST /api/lists/:id/data) is keyed by each column's key, not its label.

Field types

Twelve column types are supported:

TypeStoresOptionsNotes
textSingle-line textn/aHonors minLength / maxLength / pattern.
textareaMulti-line textn/aHonors minLength / maxLength / pattern.
numberNumbern/aHonors min / max.
booleantrue / false / nulln/aRendered as a True/False dropdown; optional fields also offer Neither (null).
dateCalendar date (YYYY-MM-DD)n/aHonors min / max (ISO date strings).
datetimeDate + time (ISO)n/aHonors min / max (ISO datetime strings).
emailEmail addressn/aBuilt-in email-format validation; pattern optional.
urlURLn/aBuilt-in URL-format validation.
telPhone numbern/aFree text; add a pattern to constrain format.
selectOne value from optionsRequiredValue must be one of options.
multiselectArray of values from optionsRequiredEach value must be in options.
priorityOne of low, medium, high, urgentOptionalDefaults to those four options when none are supplied.
{
  "name": "Support Tickets",
  "fields": [
    { "key": "subject",  "type": "text",        "label": "Subject", "required": true },
    { "key": "priority", "type": "priority",    "label": "Priority" },
    { "key": "status",   "type": "select",      "label": "Status",  "options": ["open", "pending", "closed"], "defaultValue": "open" },
    { "key": "labels",   "type": "multiselect", "label": "Labels",  "options": ["bug", "feature", "question"] },
    { "key": "due",      "type": "date",        "label": "Due date" },
    { "key": "contact",  "type": "email",       "label": "Contact email" }
  ]
}

Validation rules

Rules go in a field's validation object and are applied when a row is added or edited (POST/PUT /api/lists/:id/data), not when the schema itself is created. A failing row returns the offending column's message.

RuleApplies toEffect · error message
minnumber, date, datetimeMinimum value/date. <label> must be at least <min> (numbers) · … must be after <min> (dates).
maxnumber, date, datetimeMaximum value/date. <label> must be at most <max> · … must be before <max>.
minLengthtext, textareaMinimum length. <label> must be at least <n> characters.
maxLengthtext, textareaMaximum length. <label> must be at most <n> characters.
patterntext, textarea, emailRegex the value must match. <label> format is invalid.
stepnumberIncrement hint for the number input (UI only; not enforced on the stored value).

Type-level checks always run regardless of validation: email must be a valid address, url a valid URL, boolean a real boolean, select/multiselect values must be within options (<label> must be one of: …), and a required empty value yields <label> is required.

{
  "key": "sku",
  "type": "text",
  "label": "SKU",
  "required": true,
  "validation": { "pattern": "^[A-Z0-9-]+$", "minLength": 4, "maxLength": 20 }
}

Conditional visibility

A column can be shown or hidden based on another column's value via a visibility.condition of { field, operator, value }. field is the key of the column to watch.

{
  "key": "studentId",
  "type": "text",
  "label": "Student ID",
  "visibility": {
    "condition": { "field": "ticketType", "operator": "equals", "value": "student" }
  }
}

Supported operators:

OperatorWorks withShows the column when…
equalsanywatched value = value
notEqualsanywatched value value
containsstring, arraywatched value contains value
notContainsstring, arraywatched value does not contain value
greaterThannumber, datewatched value > value
lessThannumber, datewatched value < value
greaterThanOrEqualnumber, datewatched value value
lessThanOrEqualnumber, datewatched value value
isEmptyanywatched value is empty (value is ignored)
isNotEmptyanywatched value is non-empty (value is ignored)

Notes:

  • Visibility controls form display. A hidden column is skipped during row validation while empty, so a required column that is currently hidden by its condition is not enforced until the condition makes it visible.
  • Reference columns that appear earlier in fields: visibility is evaluated in displayOrder.
  • A single condition per column is supported (there is no and/or grouping).

A complete example

POST /api/lists
Content-Type: application/json

{
  "title": "Event Registrations",
  "isPublic": false,
  "schema": {
    "name": "Event Registrations",
    "description": "Attendees and their options.",
    "fields": [
      { "key": "attendee",   "type": "text",     "label": "Attendee",   "required": true },
      { "key": "email",      "type": "email",    "label": "Email",      "required": true },
      { "key": "ticketType", "type": "select",   "label": "Ticket type", "required": true,
        "options": ["general", "student", "vip"], "defaultValue": "general" },
      { "key": "studentId",  "type": "text",     "label": "Student ID",
        "visibility": { "condition": { "field": "ticketType", "operator": "equals", "value": "student" } } },
      { "key": "guests",     "type": "number",   "label": "Guests",     "defaultValue": 0,
        "validation": { "min": 0, "max": 5 } },
      { "key": "notes",      "type": "textarea", "label": "Notes",      "helpText": "Dietary needs, accessibility, etc." }
    ]
  }
}

Errors

Schema problems are returned as 400 with a message naming the offending column:

{ "error": "Invalid schema: Field 'year' has invalid type 'integer'. Valid types: text, number, date, datetime, boolean, select, multiselect, textarea, email, url, tel, priority", "code": "bad_request" }

Common causes: missing name, an empty fields array, a field missing key/type/label, an unknown type, a select/multiselect without options, or duplicate keys.

  • Lists (Help Center): the user-facing guide to building lists and their columns.
  • Lists: the list CRUD, rows, watchers, and connections endpoints.
  • API overview: base URL, authentication at a glance, and response conventions.
  • API explorer: try list endpoints live with the interactive Swagger console.