Forms SDK
Dos and Don'ts
The rules that keep SDK forms correct, distilled from real integrations
The short version: copy IDs and shapes from the binding map, let the SDK own what it owns, and verify by reading a response back.
Boot
- Take
apiBaseUrlfromget_sdk_binding_map; a wrong host 404s. - Author every step container after the first with
hidden, or they flash stacked during boot. - Remove
preview: trueanddebug: truebefore deploying; shipped preview mode silently stores nothing. - One engine per page life:
destroy()on teardown, neverinitagain without it (duplicate responses).
Binding
- Copy every
questionId,stepId, option key, andanswerShapefrom the binding map; a typo is silent data loss. - Set
data-question-typeon every choice group container, each control'svalueexactly the record's option key. Nodata-field-nameon choice options. - Give each control of a multi-field component an explicit
data-field-namewith the record's exact keys (workEmailAddress,phoneNumber); the set is closed, don't guessemailorphone. - Leave scheduler and file-upload questions unbound; their answers are written in code, and
validate_form_htmlknows they are exempt. - In a framework, keep bound inputs uncontrolled and mounted; the listener attaches once at
init, so no conditional rendering,keyremounts, or controlled values.
Navigation
- Let
next()andgoToStep()switch steps instantly; don't disable the continue button for a partial save or insert async work before the step change unless the owner asked for a gate. - Calling
submit()without a marker? Disable your own button until it resolves. With the marker,surface-busyhandles it. - Submit button on the last question step; both endings get a
data-step-idcontainer. The SDK reveals endings itself. - Declare
data-surface-nav="js"when navigation is code-driven; don't add fake buttons to quiet the validator. - Emit
hiddenas a constant in framework markup, never derived from state.
Data and policies
- Ask the form owner what an invalid email means (default: annotate only); never gate navigation on
validateEmailon your own initiative. Verdicts fail open on vendor outages. - Enforce required fields and selection limits in your page if the owner wants parity; the SDK never blocks a step.
- Caller-computed values go in
mergeMeta, visitor input in answers; answers are what workflows and tables read. - Check
form.capabilitiesbefore promising enrichment, validation, or tracking; the record can declare features the environment does not back.
Schedulers
- Use
mountScheduler({ form, container })and let it persist the booking; don't iframe the hosted booking page, bind the scheduler question in HTML, or hand-wire persistence unless building a custom UI on purpose. - Mount once as soon as
formresolves, even into a hidden step container; not on step entry. - Drive navigation yourself for Clari; it exposes no booking signal.
Verification
- Validate the rendered HTML with
validate_form_htmland fix every blocker; never a hand transcription of framework source. - Submit one real test response after publishing and read it back, confirming every answer and
meta.surfaceTagStatus: "sdk". The read-back is the only end-to-end truth. await form.flush()before a programmatic page close (test harnesses); ordinary visitors need nothing.- Re-run
validate_form_htmlafter every record edit and follow the editing sequence. - Walk your own branching logic in a real browser, both branches plus a boundary case; no Surface validator sees business logic in your code.