Forms
forms.submit(form, fields, options) sends a form to /_platform/forms as JSON. Digize stores the submission, finds or adds the CRM contact, adds the form to their timeline and starts the workspace’s automations for it.
const result = await digize.forms.submit( "contact", { name: "Maya Chen", email: "maya@example.com", message: "Do you ship to Canada?" }, { turnstileToken },);if (result.ok) { console.log("Sent", result.submissionId);} else { console.log(result.code, result.message, result.fields);}What it takes
Section titled “What it takes”| Argument | |
|---|---|
form |
The form’s name, such as contact or quote. Automations and the contact’s timeline go by it. |
fields |
The form’s fields: an object of strings, numbers and booleans (numbers and booleans are sent as text; null and undefined are left out), a FormData, or URLSearchParams. A FormData’s files are left out: forms take text only. |
options.turnstileToken |
The Turnstile widget’s token. Left out, it’s taken from the fields’ cf-turnstile-response, which the widget adds to its form. |
options.pageUrl |
The page the form is on. In the browser, the current page; in a Worker, the visitor’s referer. |
options.attribution |
UTM parameters and click ids, kept as the contact’s first touch. In the browser, the ones in the page’s address are sent, with these added. |
What the fields become on the contact (email, phone, name, tags, custom fields) is on the Forms page. Unlike a plain HTML post, the fields go as they are: utm_ fields stay fields unless they’re in attribution, and _ fields aren’t dropped.
What it answers
Section titled “What it answers”submit doesn’t throw when Digize turns a form down: it answers a result to show the visitor.
type FormResult = | { ok: true; submissionId: string } | { ok: false; code: DigizeErrorCode; message: string; // written for the visitor fields: Record<string, string>; // a message per field to fix error: DigizeError; };code |
Status | When |
|---|---|---|
invalid |
400 | A field needs fixing, such as neither an email nor a phone: fields.email says so. |
forbidden |
403 | The Turnstile check didn’t pass, or there was no token. See Turnstile. |
too_large |
413 | The form is over 256 KB. |
rate_limited |
429 | More than 10 posts a minute from this visitor to this site. |
unavailable |
503, or 0 | Digize couldn’t take it just now, or couldn’t be reached at all (error.status is 0 then). |
Only a client with nowhere to call (no origin or request outside a page) throws.
Turnstile
Section titled “Turnstile”Every form needs a token from Digize’s Turnstile widget, rendered with Digize’s site key. forms.settings() answers it, from GET /_platform/forms:
const { turnstileSiteKey, turnstileReady } = await digize.forms.settings();turnstileReady says whether the widget runs on the hostname the site was called on.
A token can be checked only once, so render a new one (turnstile.reset()) before sending a form again after an error.
A form on a page
Section titled “A form on a page”Render the widget with the site key, and its token rides along in the form’s FormData:
<form id="quote"> <input name="name" placeholder="Your name" /> <input name="email" type="email" /> <p class="error" data-for="email"></p> <textarea name="details"></textarea> <div id="quote-turnstile"></div> <button>Get a quote</button> <p id="quote-status"></p></form><script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>import { createSiteClient } from "@digize/sdk";
declare const turnstile: { render(element: string, options: { sitekey: string }): string; reset(widget: string): void;};
const digize = createSiteClient();const form = document.querySelector<HTMLFormElement>("#quote");const { turnstileSiteKey } = await digize.forms.settings();const widget = turnstile.render("#quote-turnstile", { sitekey: turnstileSiteKey });
form?.addEventListener("submit", async (event) => { event.preventDefault(); const result = await digize.forms.submit("quote", new FormData(form)); for (const slot of form.querySelectorAll<HTMLElement>("[data-for]")) { slot.textContent = result.ok ? "" : (result.fields[slot.dataset.for ?? ""] ?? ""); } const status = document.querySelector("#quote-status"); if (status) status.textContent = result.ok ? "Thanks, we'll be in touch." : result.message; if (!result.ok) turnstile.reset(widget);});A server-rendered page can put the key in its HTML instead, from forms.settings() in the Worker, with <div class="cf-turnstile" data-sitekey="…"> and the widget script’s automatic rendering.
A honeypot
Section titled “A honeypot”A field people never see but bots fill in, _gotcha (FORM_HONEYPOT_FIELD), catches the simplest bots: a form with it filled is thanked as usual and dropped, with nothing stored. Hide it from people and screen readers:
<input name="_gotcha" tabindex="-1" autocomplete="off" aria-hidden="true" style="position: absolute; left: -9999px" />It works the same from forms.submit and from a plain HTML form that posts to /_platform/forms.
From a Worker
Section titled “From a Worker”A site’s Worker that takes the form itself, such as a server-rendered page posting back to itself, sends it with the visitor’s request so their address goes along for the limit and Turnstile:
if (request.method === "POST" && url.pathname === "/contact") { const digize = createSiteClient({ request }); const result = await digize.forms.submit("contact", await request.formData()); return result.ok ? Response.redirect(new URL("/thanks", url), 303) : new Response(render({ errors: result.fields, message: result.message }), { status: 400, headers: { "content-type": "text/html; charset=utf-8" }, });}A form that posts straight to /_platform/forms from the browser doesn’t need the SDK in the Worker at all: handlePlatform passes it on.