React & Next.js
The @formcrest/sdk package gives you a typed submit() function and a useFormSubmit hook for handling submissions in React and Next.js.
Install
Add the SDK to your project:
npm install @formcrest/sdk
It ships ESM with TypeScript types and has no runtime dependencies. React is an optional peer dependency — you only need it for the hook, which lives on the @formcrest/sdk/react subpath.
The submit() function
The low-level submit(formId, data, options?) function works anywhere — event handlers, server code, scripts. data can be a plain object or a FormData. It resolves to a discriminated result you can narrow on ok: on success you get { ok: true, submissionId, redirect? }, and on failure { ok: false, error, message, status, fieldErrors? }. It never throws for network failures — those come back as { ok: false, status: 0 }.
import { submit } from "@formcrest/sdk";
async function send() {
const result = await submit("abc123", {
email: "test@example.com",
message: "Hello",
});
if (result.ok) {
console.log("Submitted:", result.submissionId);
} else {
console.error(result.error, result.message);
// result.fieldErrors?.email, etc.
}
}The third argument is an options object: { apiBase?, accessKey?, idempotencyKey?, signal? }. Use accessKey if the form requires one (it's sent as the access_key field), idempotencyKey for safe retries with 24-hour dedupe, and signal to pass an AbortSignal for cancellation.
The useFormSubmit hook
For React components, useFormSubmit(formId, options?) wraps submit() with submission state. It returns { status, result, isSubmitting, isSuccess, isError, submit, submitFormData, onSubmit, reset }. The onSubmit handler is built for <form onSubmit={...}>: it calls preventDefault(), reads a FormData from the form, and submits it for you. Import it from the /react subpath, and remember the "use client" directive in the Next.js App Router.
"use client";
import { useFormSubmit } from "@formcrest/sdk/react";
export function ContactForm() {
const form = useFormSubmit("abc123");
if (form.isSuccess) {
return <p>Thanks — we'll be in touch.</p>;
}
return (
<form onSubmit={form.onSubmit}>
<input type="email" name="email" required />
<textarea name="message" required />
{/* Honeypot — keep it hidden from real users. */}
<input
type="text"
name="_honey"
tabIndex={-1}
autoComplete="off"
style={{ display: "none" }}
/>
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? "Sending…" : "Send"}
</button>
{form.isError && form.result && !form.result.ok && (
<p role="alert">{form.result.message}</p>
)}
</form>
);
}Use form.status ("idle", "submitting", "success", or "error") if you prefer a single value to branch on, and form.reset() to return to the idle state. The hook also exposes form.submit(data) for posting an object and form.submitFormData(formData) if you're building the FormData yourself.
Self-hosting or staging
The endpoint defaults to https://api.formcrest.com/v1. Point the SDK at a different base — a staging environment or a self-hosted instance — by passing apiBase in the options:
const form = useFormSubmit("abc123", {
apiBase: "https://staging-api.example.com/v1",
});
// or with the function:
await submit("abc123", data, {
apiBase: "https://staging-api.example.com/v1",
});Next steps
- Submit API reference — the underlying endpoint the SDK calls.
- Error codes — the values you'll see in
result.error. - Spam protection — the honeypot field and Turnstile.