SvelteKit
A prerendered SvelteKit page whose form posts straight to Ikiform, enhanced with fetch once JavaScript loads. Works with adapter-static.
This page needs no server: the form's action is your Ikiform endpoint, so it works on adapter-static and any static host. Without JavaScript it posts and redirects like plain HTML; with JavaScript it posts with fetch and shows the result in place. To post through a form action with a secret key instead, see Svelte.
Why not use:enhance
SvelteKit's use:enhance only works with method="POST" forms that point to actions defined in a +page.server.js file. Ikiform is an outside URL, so the page uses a small onsubmit handler instead. The progressive enhancement is the same: the form works first, the script improves it.
Set it up
Add public environment variables
.env PUBLIC_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx PUBLIC_SITE_URL=https://example.comPUBLIC_SITE_URLis for the no-JavaScript redirect. While prerendering,page.url.originishttp://sveltekit-prerender, not your domain, so the redirect can't be built from it.Prerender the page
src/routes/contact/+page.js export const prerender = true;Add the form
src/routes/contact/+page.svelte <script> import { PUBLIC_IKIFORM_FORM_ID, PUBLIC_SITE_URL } from "$env/static/public"; const endpoint = `https://ikiform.com/f/${PUBLIC_IKIFORM_FORM_ID}`; let status = $state("idle"); let errors = $state({}); let message = $state(""); async function onsubmit(event) { event.preventDefault(); status = "sending"; try { const response = await fetch(endpoint, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(event.currentTarget), }); const result = await response.json(); if (result.ok) { status = "sent"; return; } errors = result.error.fields ?? {}; message = result.error.message; } catch { message = "Couldn't reach the server. Try again."; } status = "error"; } </script> {#if status === "sent"} <p>Thanks, we got it.</p> {:else} <form action={endpoint} method="POST" {onsubmit}> <label> Email <input name="email" type="email" autocomplete="email" required aria-invalid={errors.email ? "true" : undefined} /> </label> {#if errors.email}<p>{errors.email[0]}</p>{/if} <label> Message <textarea name="message" rows="5"></textarea> </label> <input type="hidden" name="_redirect" value={`${PUBLIC_SITE_URL}/contact/thanks`} /> <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden /> {#if status === "error" && !errors.email}<p role="alert">{message}</p>{/if} <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button> </form> {/if}Add a
src/routes/contact/thanks/+page.sveltefor the redirect target.
If you use SvelteKit's CSP
With kit.csp configured, allow Ikiform for both the fetch post and the plain form post. Otherwise the browser blocks one or the other.
const config = {
kit: {
csp: {
directives: {
"connect-src": ["self", "https://ikiform.com"],
"form-action": ["self", "https://ikiform.com"],
},
},
},
};
export default config;Allowed domains
List production plus your preview hosts.
| Where the form runs | Add to Allowed domains |
|---|---|
vite dev | localhost. It matches http://localhost:5173: Ikiform compares the host name only, never the port. |
| Your domain | example.com, which also covers www.example.com |
| Vercel | your-project.vercel.app. Preview URLs such as your-project-git-branch-team.vercel.app don't match it; add the exact preview host while you test one. |
| Cloudflare Pages | your-project.pages.dev. Preview URLs such as abc123.your-project.pages.dev are subdomains of it, so they already match. |
| Netlify | your-site.netlify.app. Deploy previews (deploy-preview-12--your-site.netlify.app) and branch deploys (branch--your-site.netlify.app) are not subdomains of it, so add the exact preview host while you test one. |
- Never add
vercel.app,pages.devornetlify.appon its own. Every site on that host would match, including other people's.
Test checklist
- Run the dev server with
localhostin allowed domains and submit. The thanks message should replace the form. - Run
vite buildand check the output containscontact.htmlwith the form already in it. - Turn JavaScript off, reload and submit. On your
https:domain you should land on/contact/thanks. - Submit from a preview deploy and confirm it isn't marked spam.
Troubleshooting
| What you see | Why | Fix |
|---|---|---|
The build fails saying PUBLIC_IKIFORM_FORM_ID isn't exported by $env/static/public | The variable isn't set where the build runs | Add it to .env locally and to your host's build settings. |
Nothing happens on submit, and the console shows a CORS error about the x-sveltekit-action header | use:enhance on a form that posts outside your app | Remove it and keep the onsubmit handler. |
The browser refuses to send the form because of form-action or connect-src | kit.csp doesn't list Ikiform | Add https://ikiform.com to both directives. |
| Without JavaScript you land on Ikiform's thank-you page | PUBLIC_SITE_URL is http:, or the domain isn't in allowed domains | Use your https: address and add the domain. |
Submissions arrive under the spam filter with origin_mismatch | The page that posted isn't on the form's allowed domains | Add the host from the table above. With Reject chosen, the same posts get 403 origin_not_allowed instead. |