Languages and runtimes
A typed contact form in TypeScript
There is no SDK to install, which means there are no types shipped either - so here they are. Roughly thirty lines gives you a typed submit helper with a discriminated union response, so success, validation failure and network errors are all handled at compile time rather than discovered in production.
Setup
Copy your endpoint
Create a form in the dashboard.
Drop in the helper
One file, no dependencies. Use it from React, Vue, Svelte or plain TypeScript.
TypeScript examples
Swap in your own endpoint and these run as-is.
A typed client. Copy it into your project - there is nothing to install.
const ENDPOINT = "https://send.webforms.to/wft_your_key";
/** Directives are consumed by the API rather than stored as data. */
export type Directives = {
_subject?: string;
_replyto?: string;
_cc?: string;
_from_name?: string;
_next?: string;
};
export type SubmitSuccess = {
ok: true;
id: string;
message: string;
quarantined?: boolean;
tookMs: number;
};
export type SubmitFailure = {
ok: false;
error: string;
message: string;
issues?: Array<{ field: string; message: string }>;
};
export type SubmitResult = SubmitSuccess | SubmitFailure;
export async function submitForm(
values: Record<string, unknown> & Directives,
init?: { idempotencyKey?: string; signal?: AbortSignal },
): Promise<SubmitResult> {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
...(init?.idempotencyKey
? { "idempotency-key": init.idempotencyKey }
: {}),
},
body: JSON.stringify(values),
signal: init?.signal,
});
return (await res.json()) as SubmitResult;
}The discriminated union means TypeScript narrows the result for you.
import { submitForm } from "./lib/webforms";
const result = await submitForm({
name: "Ada Lovelace",
email: "ada@example.com",
message: "Hello",
_subject: "New enquiry",
});
if (result.ok) {
console.log(result.id); // string - narrowed to SubmitSuccess
} else {
for (const issue of result.issues ?? []) {
console.error(issue.field, issue.message);
}
}TypeScript form FAQ
No, and you do not need one. The API is a single POST, so a typed helper like the one above is smaller than any package you would install and never goes out of date.
Related guides
Ready to wire up your TypeScript form?
Free for 300 submissions a month. Endpoint in under a minute, no card required.
Start free