Skip to content

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);
}
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.

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.

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.

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

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.