Skip to main content

DocsSchema & validation

Schema and validation

Model every field, answer, condition, setting, and safe schema change against the shared contract.

View as Markdownllms.txt for agents

Start from the schema shape

A form schema is the shared contract used by the builder, SDK renderers, public API, response validation, exports, and delivery. JSX and defineForm() produce the same schema. The code form id is the workspace-stable sync handle; Fillo returns a separate canonical formId for published links and submissions.

Every schema has version: 1, a title, at least one page, an ordered block list, and settings. Fields create answers. Content blocks shape the reading flow without adding answer keys.

one schema, several behaviors
import { defineForm } from "@usefillo/core";

export const supportIntake = defineForm({
  id: "support-intake",
  title: "Tell us what happened",
  pages: [
    {
      id: "request",
      blocks: [
        {
          id: "topic",
          kind: "select",
          label: "What do you need help with?",
          required: true,
          options: [
            { id: "billing", label: "Billing" },
            { id: "technical", label: "Technical issue" },
          ],
        },
        {
          id: "details",
          kind: "long_text",
          label: "Tell us more about {{topic}}",
          required: true,
          visibleIf: [{ fieldId: "topic", op: "answered" }],
        },
        {
          id: "account_id",
          kind: "hidden",
          paramName: "account",
        },
      ],
    },
  ],
  settings: { showProgress: true, saveProgress: true },
});

Treat every id as stored data

  • Use a permanent code form handle. Reusing it updates the same Fillo form; changing it creates a different sync identity.
  • Page ids must be unique within the form. Block ids must be unique across every page because field ids become keys in ResponseData.
  • Choice option ids, ranking option ids, and matrix row and column ids are stored answer values. Labels may change without rewriting those values.
  • Ids are strings from 1 to 128 characters. Never derive them from array position, visible copy, translated labels, or a random value generated during render.
  • A schema supports 1 to 50 pages, up to 500 blocks per page, and up to 200 options, rows, or columns in one list.

Renaming an id is a data change

Changing company_size to team_size does not rename historical answers. It creates a new answer key. Keep the id and change the label, or add a new field and update downstream consumers deliberately.

Field kinds and answer shapes

Every field accepts id, label, optional description, required, placeholder, and visibleIf where those properties apply. The table adds each kind's stored answer shape and field-specific rules.

KindJSXStored answerValidation and configuration
short_textFillo.TextstringOptional maxLength; otherwise a 2,000 character safety cap.
long_textFillo.LongTextstringOptional maxLength; otherwise a 20,000 character safety cap.
emailFillo.EmailstringValid email syntax and optional maxLength.
urlFillo.UrlstringAn absolute http: or https: URL, with optional maxLength.
phoneFillo.Phonestring stored in E.164 formServer validation is authoritative; defaultCountry is a two-letter country code.
numberFillo.Numberfinite numberOptional min and max; numeric strings are normalized before validation.
selectFillo.Selectoption id or Other stringOne known option id, or one free-text value up to 500 characters when allowOther is on.
multi_selectFillo.MultiSelectstring[]Unique known option ids, plus at most one Other value when enabled.
dropdownFillo.Dropdownoption id or Other stringOne known option id, or one free-text value up to 500 characters when allowOther is on.
checkboxFillo.CheckboxbooleanRequired means the value must be true; appearance may be checkbox or toggle.
ratingFillo.Ratinginteger numberFrom 1 through max, which defaults to 5. CSAT requires a 1 through 5 scale.
linear_scaleFillo.Scaleinteger numberBetween min and max, defaulting to 1 and 10. NPS requires 0 through 10.
rankingFillo.Rankingordered string[]Every option id must appear exactly once.
matrixFillo.MatrixRecord<rowId, columnId>Only declared row and column ids; a required matrix needs an answer for every row.
signatureFillo.Signaturedata:image/... stringMust be an image data URL produced by the signature control.
dateFillo.DateYYYY-MM-DD stringMust be a real calendar date, not only a string with the right shape.
file_uploadFillo.FileUploadFileValue[]Completed file references only; maxFiles, per-file size, and accept are enforced.
hiddenFillo.HiddenstringFilled from paramName or defaultValue, never rendered, and never required.
customFillo.Customarbitrary JSONCore checks required presence only; the custom component owns richer validation.

Content blocks

Content blocks belong in the same ordered blocks array as fields. They need stable block ids for durable schema structure and may carry visibleIf, but they never create a response value.

KindJSXSchema contentBehavior
headingFillo.Headingtext: stringDisplays a heading and supports answer piping.
paragraphFillo.Paragraphtext: stringDisplays supporting copy and supports answer piping.
dividerFillo.DividerNo content propertyAdds a visual break and never creates an answer.

Read and write response data by field id

  • ResponseData is an object keyed by field id. Optional unanswered fields are absent; do not depend on every schema field having a key.
  • Choice fields store option ids. Use Fillo's formatting helpers or the response formatted payload when a destination needs labels.
  • Numbers, ratings, and scales are stored as numbers after normalization. Matrix answers map row ids to column ids. Files are arrays of completed file references.
  • A custom field may store any JSON value. Its renderer and your downstream code must agree on the shape.
  • Each response is anchored to the schema version it answered. The response grid and CSV retain removed field ids, while live integrations work from the current schema.

Validation runs at schema and response boundaries

  • Fillo normalizes and validates untrusted schemas before storing or rendering them. Invalid top-level shapes, unknown kinds, duplicate ids, invalid number ranges, and unsupported schema versions fail. Malformed field blocks such as a choice with no valid options are removed, and a schema with no valid block is rejected.
  • The client gives immediate field feedback, but the submission API validates the response again. Treat the server result as authoritative.
  • Only fields visible for the submitted answers are validated and retained. A stale answer from a field hidden by logic is removed from the accepted data.
  • Strings are trimmed except custom-field strings. Numeric strings are normalized for number, rating, and scale fields. Phone answers receive authoritative server validation and E.164 normalization.
  • File answers are accepted only after the upload protocol has created and verified the referenced file for the same form.

Model choices with stable option ids

  • Store machine-friendly option ids such as technical; show respondents changeable labels such as Technical issue.
  • allowOther stores the respondent's text directly when it is not a declared option id. Downstream code must handle both declared ids and free text.
  • shuffleOptions changes presentation order only. It does not change the option list or stored values.
  • A ranking answer contains every option id in respondent order. Adding or removing ranking options changes the required answer set.
  • Matrix rows and columns each have their own stable ids. A required matrix must contain one valid column id for every row.

Use visibleIf for conditional structure

Attach visibleIf to a field or content block. Every condition in the array must match, so multiple conditions use AND behavior. Reference the controller by field id and compare choice answers to option ids, not labels.

OperatorBehavior
eqExact value match; a multi-select matches when it contains the value.
neqThe field must be answered and different from the value.
containsCase-insensitive substring for text, or membership for an array.
gt / ltFinite numeric comparison, including numeric input strings.
answeredTrue when the effective value is not empty.
not_answeredTrue when the effective value is empty.

Hidden controllers count as unanswered

If logic hides a controlling field, its stale value cannot keep a dependent field visible. This resolves chained conditions safely and matches submission validation.

Pipe answers into respondent-facing copy

Use {{field_id}} in a field label or description, or in heading and paragraph text. Fillo substitutes the current answer for display only. Choice ids become option labels, arrays join with commas, and an unanswered field becomes an empty string.

Piping does not change stored answers and is not a templating or authorization system. Never put a secret in a schema and expect a conditional block or piping token to hide it from someone who can load the published schema.

Know who owns each setting

All settings are part of FormSchema, but a browser-visible pk_ sync cannot control settings with mail or pre-submission privacy effects. On public-key sync, Fillo strips those values from a new form and preserves the dashboard value on an existing form.

SettingEffectPublic-key sync
submitModeUse a normal submit button or eligible one-answer auto-submit behavior.Incoming schema
submitLabelOverride the default submit button label.Incoming schema
successTitleSet the success screen heading.Incoming schema
successMessageSet the success screen supporting copy.Incoming schema
redirectUrlSend the respondent to an absolute HTTP or HTTPS URL after success.Incoming schema
showProgressShow progress through a multi-page form.Incoming schema
responseLimitLimit repeats by browser, identifying field, or identify context.Incoming schema
notifyEmailEmail a workspace-selected address after a response.Dashboard preserved
sendReceiptSend a receipt to the first answered email field.Dashboard preserved
saveProgressPersist in-progress answers so a respondent can resume.Incoming schema
draftAnswersVisibleLet workspace members view saved, unsubmitted answer content.Dashboard preserved
resumeEmailsSend one resume link to an eligible respondent after abandonment.Dashboard preserved
resumeUrlChoose the HTTP or HTTPS page that receives an embedded resume link.Dashboard preserved
draftDigestSend daily drop-off counts and verified respondent context, never answer content.Dashboard preserved

Private pushes have broader authority

A reviewed CLI push uses a private management token and replaces the complete schema, including dashboard-preserved keys. For a code-managed form, saveProgress and responseLimit stay code-controlled; dashboard forms edit them in Settings.

Make version-safe schema changes

  1. Prefer additive optional fields

    An optional field does not block an older client or a respondent who never sees it. A new visible required field changes what a valid submission needs.

  2. Change copy without changing ids

    Labels, descriptions, placeholders, and option labels may evolve while stable ids preserve raw answers and downstream keys.

  3. Use a new id for a new answer shape

    Changing a field from select to multi_select, or reusing an old option id for a different meaning, makes one key represent incompatible data. Add a new field instead.

  4. Review logic and piping references

    When removing a field or option, search visibleIf, piping tokens, response limits, prefill, webhooks, exports, and destination mappings for its id.

  5. Stage and test the published path

    Public-key sync stages changes on claimed live forms. Test conditional branches, validation, uploads, and a real response before publishing. A private CLI push publishes directly unless you use --draft.

Next steps

Was this page useful?

Was this page helpful?
Powered by Fillo