# Fillo API reference

Last updated: 2026-07-04

Canonical human page: https://fillo.so/docs/reference
Install docs: https://fillo.so/docs/embed.md
JSX authoring: https://fillo.so/docs/authoring.md
Styling docs: https://fillo.so/docs/styling.md
Troubleshooting: https://fillo.so/docs/troubleshooting.md

The complete runtime surface of `@usefillo/react`, `@usefillo/core`, and `@usefillo/dom`. Slot names and data-* attributes live in the styling docs; `Fillo.*` schema components live in the authoring docs.

## <FilloForm>

The framed renderer. Pass `formId` (a published, dashboard-hosted form) or `form` (a schema or a code-defined form); code forms need `client` to save responses.

| Prop | Type | What it does |
| --- | --- | --- |
| formId | string | Fetch a published form by id or slug. With only this prop, a default client (→ fillo.so) is created. |
| form | FormSchema \| CodeForm | Render a schema directly, or a defineForm() / Fillo.defineForm() form — which also syncs. |
| client | FilloClient | From createClient(). Required for code-defined forms that should save responses. |
| theme | FormTheme | Theme tokens. Precedence: appearance.theme, then this prop, then the code form's theme, then the dashboard theme. |
| appearance | FilloAppearance | The styling contract: theme + per-slot classNames + per-field overrides. See /docs/styling. |
| strings | Partial<FilloStrings> | Override any visitor-facing renderer string (localized sites). |
| components | FieldComponents | Swap any built-in field kind for your own component. |
| customComponents | CustomComponents | Renderers for your own `custom` field kinds, keyed by the field's `component`. |
| initialData | ResponseData | Pre-filled answers, keyed by field id. |
| onChange | (data: ResponseData) => void | Fires with the full answer map on every edit. |
| onSubmitted | (responseId: string \| undefined, data: ResponseData) => void | Runs after Fillo records the response. |
| onError | (error: FilloError) => void | Observe load and code-form sync failures (otherwise only logged). |
| showTitle | boolean (default true) | Render the form's own title/description header. Set false when the page already has the heading. |
| renderSuccess | () => ReactNode | Custom success screen. |
| renderError | (error: FilloError) => ReactNode | Custom error screen — receives the failure (e.g. 404 vs network). |
| className | string | Extra classes on the root element. |

## <FilloProvider> + <FormField>

The headless escape hatch: runs the same engine, renders no layout. Compose your own with `<FormField>`, `useField()`, and `useFillo()` — render every required field, or submission fails on a field the visitor can't see.

| Prop | Type | What it does |
| --- | --- | --- |
| form | FormSchema \| CodeForm (required) | The schema to run. A defineForm() form also syncs, exactly like <FilloForm>. |
| client | FilloClient | Submission (and sync) target. |
| formId | string | Explicit submission target for a plain schema. |
| appearance | FilloAppearance | Slot classes for FormField-rendered fields. |
| strings | Partial<FilloStrings> | Renderer string overrides. |
| initialData | ResponseData | Pre-filled answers. |
| onChange / onSubmitted | as on <FilloForm> | Same callbacks, same timing. |
| children | ReactNode | Your layout — fields go wherever you render them. |

`<FormField>`:

| Prop | Type | What it does |
| --- | --- | --- |
| id | string (required) | Which field to render. Returns null for unknown ids and for fields hidden by conditional logic. |
| components | FieldComponents | Per-kind overrides, scoped to this one field. |
| customComponents | CustomComponents | Custom-kind renderers, scoped to this one field. |

## <Fillo.Form> extras

JSX authoring (see https://fillo.so/docs/authoring.md). Besides every `<FilloForm>` prop above (except `form`/`formId`), it takes the schema-level props:

| Prop | Type | What it does |
| --- | --- | --- |
| id | string (required) | Workspace handle — the form's identity across syncs. |
| title / description | string | Form header copy, compiled into the schema. |
| settings | FormSettings | The settings.* keys below, compiled into the schema. |
| theme | FormTheme | Theme tokens, compiled into the CodeForm (they sync to the hosted page). |
| client | FilloClient | Sync + submission target. |
| children | Fillo.* elements | The schema: pages or blocks. Never rendered — compiled. |

## Hooks

`useFillo()` returns the full engine API (throws outside `<FilloForm>` / `<FilloProvider>`):

| Member | Type | What it is |
| --- | --- | --- |
| form | FormSchema | The normalized schema being rendered. |
| formId | string \| undefined | Submission target, once known (code forms resolve it after sync). |
| client | FilloClient \| undefined | The client in use. |
| data | ResponseData | Current answers, keyed by field id. |
| errors | Record<string, string> | Validation messages, keyed by field id. |
| setValue | (fieldId, value) => void | Write an answer. Clears that field's error; re-checked on next/submit. |
| pageIndex / pageCount | number | Current page (0-based) and total pages. |
| page | FormPage | The current page. |
| blocks | Block[] | The current page's blocks after conditional-visibility logic. |
| isFirstPage / isLastPage | boolean | Position flags for your own footer. |
| next() / back() | () => void | Page navigation. next() validates the current page first and stays put on errors. |
| submit() | () => Promise<void> | Validates everything, submits, and settles state. Rejections also land in submitError. |
| status | "idle" \| "submitting" \| "submitted" \| "error" | Engine status (also on the root as data-state). |
| uploading | boolean | True while any file upload is in flight — submit is blocked. |
| submitError | string \| undefined | Message of the last failed submit; cleared on edit/retry. |
| setUploading | (fieldId, busy) => void | Used by upload fields to gate submission. |

`useField(id)` drives one field:

| Member | Type | What it is |
| --- | --- | --- |
| field | Field \| undefined | The field's schema entry, or undefined if no field has this id. |
| value | FieldValue | The current answer. |
| error | string \| undefined | The current validation message. |
| setValue | (value: FieldValue) => void | Write the answer. |

`useFilloController(options)` is the React binding both components use — the same options as `<FilloProvider>`. Outside React, the identical engine is `createFormController()` from `@usefillo/core` or `@usefillo/dom`.

```tsx
import { FilloProvider, FormField, useField, useFillo } from "@usefillo/react";

function SubmitBar() {
  const { isLastPage, next, submit, status } = useFillo();
  return (
    <button onClick={() => (isLastPage ? void submit() : next())}>
      {status === "submitting" ? "Sending…" : isLastPage ? "Send" : "Next"}
    </button>
  );
}

function BareEmail() {
  const { value, error, setValue } = useField("email");
  return <input value={String(value ?? "")} onChange={(e) => setValue(e.target.value)} aria-invalid={!!error} />;
}
```

## createClient

```ts
import { createClient } from "@usefillo/react"; // or "@usefillo/dom"

const client = createClient({
  key: process.env.NEXT_PUBLIC_FILLO_KEY!, // pk_… — public by design
});
```

| Option | Type | What it does |
| --- | --- | --- |
| key | string | Publishable workspace key (pk_…) — safe to ship in client code. Required only for syncing code-defined forms. |
| baseUrl | string | Target a different Fillo server (staging, tests, a proxy on your own domain). Wins over sameOrigin. |
| sameOrigin | boolean | Internal: Fillo's own first-party pages only. Not for embedding. |
| fetch | typeof fetch | Custom fetch implementation. |

Methods (requests time out after 30s; failures throw `FilloError` with `status` and, on 429, `retryAfterSec`):

| Method | Returns | What it does |
| --- | --- | --- |
| getForm(idOrSlug) | Promise<PublishedForm> | Fetch a published form: { id, slug, schema, theme, closed?, branding? }. |
| submit(formId, data, meta?) | Promise<SubmitResult> | Record a response. Validation failure returns { ok: false, errors } (per field) instead of throwing. |
| syncForm(handle, schema, theme?) | Promise<SyncFormResult> | Upsert a code-defined form into the workspace. Needs `key`; returns the canonical formId and lifecycle status. |
| uploadFile(formId, file, { fieldId, onProgress?, signal?, sessionId?, uploadToken? }) | Promise<FileValue> | Resumable chunked upload straight to the form's storage; the FileValue goes into response data. |

## settings.*

On `defineForm({ settings })`, `<Fillo.Form settings={…}>`, or the dashboard. All optional.

| Key | Type | What it does |
| --- | --- | --- |
| submitMode | "button" \| "auto" | Default "button". "auto" submits after a single discrete answer and hides the final submit button — one-tap votes, CSAT, NPS. |
| submitLabel | string | Custom submit button text. |
| successTitle | string | Thank-you screen title (wins over the strings default). |
| successMessage | string | Thank-you screen body (wins over the strings default). |
| redirectUrl | string | Redirect after submit instead of the success screen. http(s) only. |
| showProgress | boolean | Progress bar on multi-page forms (default true). |
| submissionLimit | "multiple" \| "once_per_visitor" | Default "multiple". once_per_visitor remembers this browser answered and sends a server-side de-duplication key. |
| notifyEmail | string | Notify this address on every submission. |
| sendReceipt | boolean | Email respondents a receipt (to the first answered email field). |

## Theme tokens

Generated from `FILLO_THEME_VARS` (exported by `@usefillo/core`). Tokens with a FormTheme key are set inline by the `theme` prop / `defineForm({ theme })` and sync to the hosted page; the rest are stylesheet-only — override them from your CSS on any ancestor. `FormTheme.colorScheme` is not a variable: it sets `data-fillo-color-scheme` on the root.

| CSS variable | FormTheme key | What it paints |
| --- | --- | --- |
| --fillo-primary | primary | Buttons, focus rings, selection. |
| --fillo-bg | background | Form background. |
| --fillo-text | text | Body text. |
| --fillo-radius | radius | Corner radius scale. |
| --fillo-font | fontFamily | Font family. |
| --fillo-muted |  | Descriptions, hints, placeholders. |
| --fillo-border |  | Input borders, dividers. |
| --fillo-control-bg |  | Input backgrounds. |
| --fillo-error |  | Validation messages. |
| --fillo-primary-contrast |  | Text on primary buttons. |

## Strings

Generated from `DEFAULT_STRINGS` (exported by `@usefillo/core`). Override any subset via the `strings` prop; schema-authored text (labels, settings success copy) always wins.

| Key | Default | Shown |
| --- | --- | --- |
| back | Back | Back button on multi-page forms. |
| next | Next | Next button between pages. |
| submit | Submit | Submit button on the last page (settings.submitLabel wins). |
| submitting | Submitting… | Submit button while the request is in flight. |
| uploading | Uploading… | Submit button while a file upload is in flight. |
| optional |  (optional) | Suffix on non-required field labels. |
| other | Other | The "Other" free-text choice row. |
| otherPrompt | Other — please specify | The "Other" dropdown entry prompt. |
| otherPlaceholder | Your answer | Placeholder of the "Other" free-text input. |
| choosePlaceholder | Choose… | Dropdown placeholder when the field sets none. |
| successTitle | Thanks! | Success screen title (settings.successTitle wins). |
| successMessage | Your response has been recorded. | Success screen body (settings.successMessage wins). |
| closed | This form is no longer accepting responses. | The form is no longer accepting responses. |
| notLive | This form isn't live yet. | An unpublished code form, in production. |
| submitFailed | This form can't submit right now. Please try again in a moment. | Fallback when a submit fails without a server message. |
| loadFailedNotFound | Form not found — check the form id and that it's published. | A hosted form id/slug that doesn't resolve (404). |
| loadFailedNetwork | Couldn't reach the server — check your connection or CORS. | The load request never reached the server. |
| loadFailed | This form could not be loaded. | Any other load failure. |
| renderFailed | This form could not be rendered. | The schema failed validation and can't render. |

## @usefillo/dom

`renderForm(target, options)` — `target` is an element or a selector string; returns a handle.

```ts
import { renderForm } from "@usefillo/dom";
import "@usefillo/dom/styles.css";

const instance = renderForm("#form", {
  formId: "customer-onboarding",
  onSubmitted: (id, data) => console.log("response", id, data),
});

// instance.setValue("email", "jane@example.com");
// await instance.submit();
// instance.destroy();
```

| Option | Type | What it does |
| --- | --- | --- |
| form | FormSchema \| CodeForm | Render a schema or a defineForm() form (which also syncs). |
| formId | string | Fetch a published form by id or slug (a default client is created if none is passed). |
| client | FilloClient | Submission (and sync) target. |
| theme | FormTheme | Theme tokens applied to the root. |
| initialData | ResponseData | Pre-filled answers. |
| className | string | Extra classes on the rendered form. |
| components | Partial<Record<FieldKind, FieldRenderer>> | Swap any built-in field kind; a FieldRenderer returns an HTMLElement. |
| customComponents | Record<string, FieldRenderer> | Renderers for your own `custom` field kinds. |
| onChange / onSubmitted / onError | as on <FilloForm> | Same callbacks, same timing. |
| renderSuccess | (api: FilloDomApi) => HTMLElement | Custom success screen. |
| renderError | (error: FilloError) => HTMLElement | Custom error screen. |

The returned handle:

| Member | Type | What it is |
| --- | --- | --- |
| element | HTMLElement | The rendered root. |
| status | FormStatus \| "loading" \| "closed" | Engine status, plus the wrapper-only fetching/closed states. |
| data | ResponseData | Current answers. |
| form | FormSchema \| null | The resolved schema (null while loading). |
| setValue(fieldId, value) | void | Write an answer programmatically. |
| next() / back() | void | Page navigation (next validates first). |
| submit() | Promise<void> | Validate and submit. |
| destroy() | void | Tear down the DOM and every listener. |

Also exported: `createFormElement(options)` (build a detached node), the `<fillo-form>` web component (`registerFilloElement()`; `publishable-key` / `form-id` attributes, `fillo-change` / `fillo-submit` / `fillo-error` events), and `createFormController()` — the render-nothing engine for Vue, Svelte, or vanilla layouts (`getState`, `subscribe`, `setValue`, `next`, `back`, `submit`, `destroy`).
