JavaScript framework
Svelte form handling without a backend
A standalone Svelte app (without SvelteKit's server) compiles to static assets, so there is no backend to receive a form. Formward fills that gap: your component posts the data to one EU-hosted URL and reads a JSON reply, no server code required.
The component below keeps a single status variable. The submit handler prevents the default navigation, sends FormData with fetch(), and updates status so the markup can react. Svelte's reactivity means the UI follows the variable with no extra wiring.
EU-hosted · Compiled Svelte ships anywhere, but every submission lands on an EU form backend - keeping personal data inside the EU/EEA by default.
The Svelte example
Copy this into your project and replace <FORM_ID> with the id of a form you create in the Formward dashboard.
svelte
<script>
let status = "idle"; // idle | sending | ok | error
async function onSubmit(e) {
status = "sending";
const form = e.target;
try {
const res = await fetch("https://forms.formward.eu/f/<FORM_ID>", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const data = await res.json(); // { ok: true, id, files }
if (!res.ok || !data.ok) throw new Error(data.error || res.status);
form.reset();
status = "ok";
} catch {
status = "error";
}
}
</script>
{#if status === "ok"}
<p>Thanks - your message is on its way.</p>
{:else}
<form on:submit|preventDefault={onSubmit}>
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
style="display:none" aria-hidden="true" />
<button disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{#if status === "error"}<p role="alert">Could not send. Try again.</p>{/if}
</form>
{/if}Success and error handling
- on:submit|preventDefault is Svelte's shorthand for calling e.preventDefault(), so the fetch() takes over from the browser's default POST.
- Reassigning status (not mutating it) is what triggers Svelte's reactivity - each state change re-renders the relevant block.
- On an error status Formward returns { ok: false, error }; surface data.error if you want the exact reason (validation, plan limit, and so on).
Spam protection
Every example above includes the _gotcha honeypot field. It is hidden from real users and must stay empty. When a bot fills it, Formward files the submission as spam and still answers with a normal success response, so the bot learns nothing and nothing reaches your inbox. Rate limits and the origin allowlist are checked on Formward's side as well. For a stricter gate, switch on Cloudflare Turnstile in the form's settings and add the widget; it adds a cf-turnstile-response field that Formward verifies on receipt. Turnstile is optional, and Cloudflare is a US provider, listed as such in the sub-processor register. The spam docs have the widget markup.
The JSON response
A standard POST gets a 302 redirect (or your thank-you page). Send the Accept: application/json header - as every fetch() example above does - and Formward returns JSON instead:
HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true, "id": "3f2a9c1e-...", "files": 0, "success": { "title": "Thank you!", ... } }files is the number of uploaded files stored and success carries the confirmation text from the form's settings. When you set a redirect URL for the form, the reply also has a redirect field your code can send the visitor to. On failure the body is { ok: false, error } with a 4xx status: 422 when required consent is missing or a field is too long, 403 when a Turnstile check fails, 429 for too many submissions from one address, 402 when the account is over its plan limit. See responses and redirects for the full table and the AJAX docs for CORS.
Other frameworks
Collect your first Svelte submission
Create a form, paste the snippet, and keep every submission in the EU. GDPR-clean from the first POST.