Skip to main content

SDK reference

Practical embedding reference for @usefillo/react, @usefillo/core, and @usefillo/dom. It covers the renderers, hooks, client, settings, tokens, and strings used to embed forms. Lower-level core utilities remain available through the installed package types.

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

PropTypeWhat it does
formIdstringFetch a published form by id or slug. With only this prop, a default client (→ fillo.so) is created.
formFormSchema | CodeFormRender a schema directly, or a defineForm() / Fillo.defineForm() form — which also syncs.
clientFilloClientFrom createClient(). Required for code-defined forms that should save responses.
themeFormThemeTheme tokens. Precedence: appearance.theme, then this prop, then the code form's theme, then the dashboard theme.
appearanceFilloAppearanceThe styling contract: theme + per-slot classNames + per-field overrides, detailed in the styling guide.
stringsPartial<FilloStrings>Override any visitor-facing renderer string (localized sites).
componentsFieldComponentsSwap any built-in field kind for your own component.
customComponentsCustomComponentsRenderers for your own `custom` field kinds, keyed by the field's `component`.
initialDataResponseDataPre-filled answers, keyed by field id.
respondentFilloRespondentidentify(): your app's account context ({ id, email?, name?, traits? } — id is your own user id). Recorded with the response as an unverified claim so responses, webhooks, and integrations say who answered. Safe to pass late, after your session loads.
challengeChallengeConfigHuman-verification widget config ({ provider, siteKey } — the public site key only; the secret stays server-side). Normally read from the form fetch automatically; pass it only when rendering an inline `form` schema that should still show the check.
uploadFileSizeLimitMbnumberServer-owned per-file ceiling when an inline schema skips the normal form fetch. Fetched and code-defined forms receive it automatically.
onChange(data: ResponseData) => voidFires with the full answer map on every edit.
onSubmitted(responseId: string | undefined, data: ResponseData) => voidRuns after Fillo records the response.
onError(error: FilloError) => voidObserve load and code-form sync failures (otherwise only logged).
showTitleboolean (default true)Render the form's own title/description header. Set false when the page already has the heading.
renderSuccess() => ReactNodeCustom success screen.
renderError(error: FilloError) => ReactNodeCustom error screen — receives the failure (e.g. 404 vs network).
classNamestringExtra 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.

PropTypeWhat it does
formFormSchema | CodeForm (required)The schema to run. A defineForm() form also syncs, exactly like <FilloForm>.
clientFilloClientSubmission (and sync) target.
formIdstringExplicit submission target for a plain schema.
appearanceFilloAppearanceSlot classes for FormField-rendered fields.
stringsPartial<FilloStrings>Renderer string overrides.
initialDataResponseDataPre-filled answers.
respondentFilloRespondentYour app's account context for identify(). It is an unverified claim unless the project identity secret validates respondent.hash.
onChange / onSubmittedas on <FilloForm>Same callbacks, same timing.
childrenReactNodeYour layout — fields go wherever you render them.

<FormField> renders one field with the default (or overridden) component, in your layout:

PropTypeWhat it does
idstring (required)Which field to render. Returns null for unknown ids and for fields hidden by conditional logic.
componentsFieldComponentsPer-kind overrides, scoped to this one field.
customComponentsCustomComponentsCustom-kind renderers, scoped to this one field.

<Fillo.Form> extras

The JSX authoring root — see the code-defined forms guide. Besides every <FilloForm> prop above (except form/formId), it takes the schema-level props:

PropTypeWhat it does
idstring (required)Project handle — the form's identity across syncs.
title / descriptionstringForm header copy, compiled into the schema.
settingsFormSettingsThe settings.* keys below, compiled into the schema.
themeFormThemeTheme tokens, compiled into the CodeForm (they sync to the hosted page).
clientFilloClientSync + submission target.
childrenFillo.* elementsThe schema: pages or blocks. Never rendered — compiled.

Hooks

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

MemberTypeWhat it is
formFormSchemaThe normalized schema being rendered.
formIdstring | undefinedSubmission target, once known (code forms resolve it after sync).
clientFilloClient | undefinedThe client in use.
dataResponseDataCurrent answers, keyed by field id.
errorsRecord<string, string>Validation messages, keyed by field id.
setValue(fieldId, value) => voidWrite an answer. Clears that field's error; re-checked on next/submit.
pageIndex / pageCountnumberCurrent page (0-based) and total pages.
pageFormPageThe current page.
blocksBlock[]The current page's blocks after conditional-visibility logic.
isFirstPage / isLastPagebooleanPosition flags for your own footer.
next() / back()() => voidPage 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).
uploadingbooleanTrue while any file upload is in flight — submit is blocked.
submitErrorstring | undefinedMessage of the last failed submit; cleared on edit/retry.
restoredSubmissionbooleanTrue when status is "submitted" because a browser response limit (responseLimit.by "browser") restored a prior visit, not a submit in this mount. Skip one-time reactions (focus moves, redirects, confetti) when set — remounts replay them otherwise.
resumedDraftbooleanTrue when a saved-progress draft (settings.saveProgress) restored answers or page position from a previous visit. Render a resume notice with a Start over action when set.
editingPreviousbooleanTrue when a verified respondent's previous response was loaded for update-in-place editing.
flushDraft()() => voidPersist unsaved draft progress now. FilloProvider and FilloForm already call it on pagehide/visibility-hidden.
resetDraft()() => voidDiscard the saved draft and reset to a fresh fill — the Start over action.
setUploading(fieldId, busy) => voidUsed by upload fields to gate submission.

useField(id) drives one field — the lowest-level hook:

MemberTypeWhat it is
fieldField | undefinedThe field's schema entry, or undefined if no field has this id.
valueFieldValueThe current answer.
errorstring | undefinedThe current validation message.
setValue(value: FieldValue) => voidWrite the answer.

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

hooks in a headless layout
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

one client per app
import { createClient } from "@usefillo/react"; // or "@usefillo/dom"

const client = createClient({
  key: process.env.NEXT_PUBLIC_FILLO_KEY!, // pk_… — public by design
});
OptionTypeWhat it does
keystringPublishable project key (pk_…) — safe in client code for code-form sync and registered-schema resolution. Published form reads and submissions do not use it; schema writes depend on project sync authorization.
baseUrlstringTarget a different Fillo server (staging, tests, a proxy on your own domain). Wins over sameOrigin.
sameOriginbooleanInternal: Fillo's own first-party pages only. Not for embedding.
fetchtypeof fetchCustom fetch implementation.

Methods. Ordinary JSON requests time out after 30 seconds; upload operations use provider-specific limits. Failures throw FilloError with status and, on 429, retryAfterSec:

MethodReturnsWhat it does
getForm(idOrSlug)Promise<PublishedForm>Fetch a published form: { id, slug, schema, theme, closed?, branding?, challenge? }.
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>Create or update a code-defined form in the workspace. Needs `key`; returns the formId and sync status — every result field is in the SyncFormResult table below.
fetchOwnResponse(formId, respondent)Promise<{ responseId, data } | null>Fetch a verified respondent's existing response for update-in-place editing. Unverified or missing responses return null.
startSession(formId, pageCount)Promise<string | null>Start best-effort funnel tracking. The built-in renderers call this automatically.
reportProgress(sessionId, data)voidReport furthestPage or completed for a session. Best-effort and non-blocking.
createDraft(formId, body)Promise<CreatedDraft>Create saved progress and return its one-time ownership token. The built-in renderers manage this automatically.
getDraft(draftId, token, adopt?)Promise<ResponseDraft>Read saved progress. adopt rotates a single-use resume-link token.
saveDraft(draftId, token, body, opts?)Promise<void>Replace a draft's answers/page and extend its expiry.
deleteDraft(draftId, token)Promise<void>Discard saved progress (the Start over path).
getUploadSession(sessionId, token?, signal?)Promise<UploadSession>Read an upload session before resuming it.
uploadFile(formId, file, { fieldId, onProgress?, onSession?, signal?, sessionId?, uploadToken? })Promise<FileValue>Browser-direct upload to the form's storage. Use onSession to persist the returned sessionId/uploadToken handle, then pass it back with the same file to resume after interruption.

settings.* keys

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

Operational and privacy controls stay dashboard-owned: notifyEmail, sendReceipt, draftAnswersVisible, resumeEmails, resumeUrl, and draftDigest. Public-key sync preserves their configured values and ignores incoming changes.

KeyTypeWhat 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.
submitLabelstringCustom submit button text.
successTitlestringThank-you screen title (wins over the strings default).
successMessagestringThank-you screen body (wins over the strings default).
redirectUrlstringRedirect after submit instead of the success screen. http(s) only.
showProgressbooleanProgress bar on multi-page forms (default true).
responseLimit{ by, field?, scopeField?, onRepeat }Limit repeat responses; absent means no limit. by: "browser" (same device), "field" (a contact answer — set field to the email or phone field id), or "identify" (the signed-in respondent). Optional scopeField sub-scopes the limit by another field's answer, e.g. one per browser per article. onRepeat: "keep" the first answer, or "update" it in place (identify only).
trust{ unverified?, challenge? }Per-form submission-trust policy; absent keeps the default accept-everything behavior. unverified: "quarantine" HOLDS submissions from respondents that are not HMAC-verified: stored but withheld from webhooks, integrations, notifications, receipts, exports, analytics, and the default responses grid until an owner releases them. The policy is fail-closed: without a project identity secret, every submission is unverified and held. challenge: "off" (default) or "turnstile" — require a Cloudflare Turnstile human check that the SDK renders (inside a small Fillo-hosted frame, so it works on any embedding domain with no Cloudflare setup) and Fillo verifies server-side before accepting a submission (a missing or forged token is rejected when the deployment has Turnstile keys — always true on Fillo's hosted service; a keyless self-host accepts submissions and emits telemetry). The two controls are independent; a form can use both.
saveProgressbooleanAutosave in-progress answers and restore them on return (default false).
notifyEmailstringNotify this address on every submission. Dashboard-owned; public-key sync preserves the configured value and ignores incoming changes.
sendReceiptbooleanEmail respondents a receipt (to the first answered email field). Dashboard-owned; public-key sync preserves the configured value and ignores incoming changes.
draftAnswersVisiblebooleanLet workspace members read unsubmitted saved-progress answers (default false; dashboard-owned; requires saveProgress).
resumeEmailsbooleanSend one answer-free resume link after a saved-progress form is idle (default false; dashboard-owned; requires saveProgress).
resumeUrlstringHTTP(S) destination for embedded-form resume links (dashboard-owned); the draft reference travels in the URL fragment.
draftDigestbooleanSend the notification address a daily saved-progress drop-off summary without answer content (default false; dashboard-owned; requires notifyEmail).

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: when present it sets data-fillo-color-scheme on the root. Omit it to inherit the host page’s CSS color scheme; use auto only for a deliberately system-driven page. A fixed hex background derives a readable palette unless the scheme is explicitly light or dark.

CSS variableFormTheme keyWhat it paints
--fillo-primaryprimaryButtons, focus rings, selection.
--fillo-bgbackgroundForm background.
--fillo-texttextBody text.
--fillo-radiusradiusCorner radius scale.
--fillo-fontfontFamilyFont family.
--fillo-mutedstylesheet-onlyDescriptions, hints, placeholders.
--fillo-borderstylesheet-onlyInput borders, dividers.
--fillo-control-bgstylesheet-onlyInput backgrounds.
--fillo-errorstylesheet-onlyValidation messages.
--fillo-primary-contraststylesheet-onlyText on primary buttons.

Strings

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

KeyDefaultShown
backBackBack button on multi-page forms.
nextNextNext button between pages.
submitSubmitSubmit button on the last page (settings.submitLabel wins).
submittingSubmitting…Submit button while the request is in flight.
uploadingUploading…Submit button while a file upload is in flight.
optional (optional)Suffix on non-required field labels.
otherOtherThe "Other" free-text choice row.
otherPromptOther — please specifyThe "Other" dropdown entry prompt.
otherPlaceholderYour answerPlaceholder of the "Other" free-text input.
choosePlaceholderChoose…Dropdown placeholder when the field sets none.
successTitleThanks!Success screen title (settings.successTitle wins).
successMessageYour response has been recorded.Success screen body (settings.successMessage wins).
closedThis form is no longer accepting responses.The form is no longer accepting responses.
notLiveThis form isn't live yet.An unpublished code form, in production.
notOpenTitleThis form isn't open yetTitle of the not-open state for a draft/storage-blocked form in production.
notOpenBodyThe form owner is still setting things up. Please check back soon.Body of the not-open state.
closedTitleResponses are closedTitle of the closed-flavor state (expired/capped workspace); its body reuses `closed`.
submitFailedThis form can't submit right now. Please try again in a moment.Fallback when a submit fails without a server message.
loadFailedNotFoundForm not found. Check the link or ask the form owner for help.A hosted form id/slug that doesn't resolve (404).
loadFailedNetworkCouldn't reach the server — check your connection and try again.The load request never reached the server.
loadFailedThis form could not be loaded.Any other load failure.
renderFailedThis form could not be rendered.The schema failed validation and can't render.
resumeNoticePicked up where you left off.Saved-progress notice when a draft restored earlier answers.
editNoticeYou're updating your earlier response.Update-in-place notice (responseLimit onRepeat "update") when the person's previous response was prefilled for editing.
resumeStartOverStart overThe discard action next to the resume notice.
challengeUnavailableThe verification check couldn't load. Refresh the page and try again.Shown when the human-verification widget can't load (script blocked).

@usefillo/dom

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

render + drive
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();
OptionTypeWhat it does
formFormSchema | CodeFormRender a schema or a defineForm() form (which also syncs).
formIdstringFetch a published form by id or slug (a default client is created if none is passed).
clientFilloClientSubmission (and sync) target.
themeFormThemeTheme tokens applied to the root.
initialDataResponseDataPre-filled answers.
respondentFilloRespondentYour app's account context for identify(); late-bind it with setRespondent().
classNamestringExtra classes on the rendered form.
componentsPartial<Record<FieldKind, FieldRenderer>>Swap any built-in field kind; a FieldRenderer returns an HTMLElement.
customComponentsRecord<string, FieldRenderer>Renderers for your own `custom` field kinds.
onChange / onSubmitted / onErroras on <FilloForm>Same callbacks, same timing.
renderSuccess(api: FilloDomApi) => HTMLElementCustom success screen.
renderError(error: FilloError) => HTMLElementCustom error screen.

The returned handle:

MemberTypeWhat it is
elementHTMLElementThe rendered root.
statusFormStatus | "loading" | "closed"Engine status, plus the wrapper-only fetching/closed states.
dataResponseDataCurrent answers.
formFormSchema | nullThe resolved schema (null while loading).
setValue(fieldId, value)voidWrite an answer programmatically.
next() / back()voidPage navigation (next validates first).
submit()Promise<void>Validate and submit.
setRespondent(respondent)voidLate-bind or clear identify() context after the host session resolves.
flushDraft()voidPersist pending saved-progress answers now.
resetDraft()voidDiscard saved progress and start over.
destroy()voidTear 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.

This page for agents: /docs/reference.md · index at /llms.txt

Updated

Was this page helpful?