The custom-form module renders an author-defined form as a step in the verification journey. The fields, labels, and copy are defined in your workflow template and delivered to the SDK; the module renders one control per field, collects the answers, uploads any files, and submits the step. On success it emits checktiv.custom_form.submitted so your page can advance.
Unlike the IDV module (which runs inside a sandboxed iframe), the custom-form module renders real form controls into your page. That means you must include its stylesheet.
Register the module and its stylesheet
Section titled “Register the module and its stylesheet”Import the module for its self-registration side effect, and import the required stylesheet so the controls are styled:
import { init } from '@checktiv/sdk-web';import '@checktiv/sdk-web/custom-form'; // registers the custom_form moduleimport '@checktiv/sdk-web/custom-form/style.css'; // required: styles the rendered controlsOn the CDN script-tag path (window.Checktiv), the module is registered for you, and the stylesheet ships as a sibling file to <link> alongside the bundle.
Mount it
Section titled “Mount it”The default path is mountProvisioned: when the server declares a custom_form step, the SDK renders it and injects the applicant-safe form config and copy for you.
const client = init({ publishableKey: 'ah_pk_us_test_...', getSessionToken });
client.mountProvisioned({ target: document.getElementById('verify-container'), onEvent: (event) => { if (event.type === 'checktiv.custom_form.submitted') { // The step was accepted. Advance to the next step. window.location.reload(); } },});If you resolve the applicant-safe config yourself (for example, a host that renders the form on a step it already controls), mount the module explicitly and pass the config and copy:
client.mount('custom_form', { target: document.getElementById('verify-container'), config, // the applicant-safe form config: { fields, supportedLocales } copy, // per-field and chrome strings, resolved to the applicant's locale onEvent: (event) => { if (event.type === 'checktiv.custom_form.submitted') { window.location.reload(); } },});Config and copy are injected
Section titled “Config and copy are injected”The module is locale-agnostic by design. It never reads navigator.language or bundles a translation runtime. Instead:
- Config (
{ fields, supportedLocales }) is the applicant-safe form definition. Author-only fields (rules, notification targets) are stripped before they reach the browser, so nothing sensitive is rendered. - Copy is a map of plain strings your host already resolved to the applicant’s locale (for example,
field.label[locale]). Absent keys fall back to a built-in English table, which is what a CDN script-tag customer gets. The chrome strings include both labels of the submit control:submit(idle, “Continue”) andsubmitPending(in flight, “Submitting…”). Supply them together if you localize them, since they are two states of one control.
Every author-supplied string renders as plain text, never as HTML, so author copy cannot inject markup into your page.
The submitted event advances the journey
Section titled “The submitted event advances the journey”The module emits three events, all in the checktiv.custom_form.* namespace:
| Event type | Meaning |
|---|---|
checktiv.custom_form.ready |
The form config resolved and the fields rendered. The applicant can begin. |
checktiv.custom_form.submitted |
The step submit succeeded. Advance the journey (for example, reload into the next step). This is terminal for the step, not a verdict: rule evaluation happens on the server. |
checktiv.custom_form.error |
A failure (session unresolved, upload failed, submit failed). Carries an actionable error. |
Do not exhaustively switch on event.type. New event types may be added without a major version bump. See Versioning.
A multi-step journey reloads between steps
Section titled “A multi-step journey reloads between steps”There is no in-place advance, on this module or any other. The model is the same everywhere: the module submits, emits its submitted event, and your page reloads or navigates so the substrate re-resolves the session and mounts the module for the next step. The snippets above reload for exactly this reason, and it is the supported way to move a journey forward rather than a placeholder for something better.
What follows from it, if your workflow has more than one SDK-rendered step:
- Expect one reload per step boundary. Two
custom_formscreens reload once between them, and so does anid_verificationstep followed by acredit_historystep. - Keep anything you need across the boundary in your own storage. Module state does not survive the reload, and it is not meant to. Your own
localStorageor your server is the place for it. - A
collect_user_infostep is not one of these boundaries. It is satisfied before the mount rather than rendered by it, either from the inlineapplicantfields onPOST /v1/sessionsor throughcollectUserInfo({ session }). See Embedded-workflow renderability, which acredit_historyworkflow always has to deal with.
Content-only screens still advance
Section titled “Content-only screens still advance”A screen with zero input fields (only content blocks, such as a disclosure to acknowledge) still renders a Continue control. Pressing it submits an empty answer set and advances the journey. This is intentional: without a Continue control, a content-only step would never complete and the workflow would wait forever.
The submit control shows its progress
Section titled “The submit control shows its progress”From the moment the applicant presses it until the submit settles, the control is disabled, carries aria-busy, swaps to the submitPending label, and announces itself through a polite live region beside it. The busy state covers the file uploads too, not just the final request, because the uploads are the longest part of the interaction.
It clears on every failure, so the applicant can correct an answer and press again. It deliberately does NOT clear on success: your page navigates away on checktiv.custom_form.submitted, and keeping the control disabled until it does is what prevents a double submit.
File fields
Section titled “File fields”When a field accepts a file, the module uploads the file from the browser directly to storage before it submits the answers, so file bytes never pass through your server. If an upload fails, the module emits checktiv.custom_form.error with upload_failed; the applicant can retry.
Related pages
Section titled “Related pages”- Modules overview - the full public surface and the self-registration model
- Quickstart - end-to-end integration walkthrough
- Error reference - every error code and its recovery step
- Verdict and webhooks - the authoritative outcome arrives on your server