HTML forms
Post any HTML form to Ikiform with no JavaScript: redirects, reserved fields, arrays and the honeypot.
A plain HTML form is the whole integration. No script, no iframe and no embed: set the action to your form's endpoint and keep your own markup and CSS. This also works in site builders that let you paste custom HTML; see Site builders.
A basic form
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label>
Name
<input name="name" autocomplete="name" required />
</label>
<label>
Email
<input name="email" type="email" autocomplete="email" required />
</label>
<label>
Message
<textarea name="message" rows="5"></textarea>
</label>
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Send</button>
</form>methodmust bepost. The browser sendsapplication/x-www-form-urlencoded, ormultipart/form-dataif you setenctype. Both work.- Every control with a
namebecomes a field. Controls without a name, and disabled controls, are not sent by the browser. - Values are stored as UTF-8 text, normalized to NFC, with CRLF line endings turned into LF and control characters removed.
- Browser validation such as
requiredandtype="email"helps people, but it is not a security check. Define fields to have Ikiform enforce rules on the server.
After submit
A successful browser post is answered with a 303 redirect. Ikiform picks the target in this order:
- The form's Redirect after submit setting in the dashboard. Any
http:orhttps:URL works here, because only workspace members can set it. - A
_redirect(or_next) field in the post, if it passes the rules below. - Otherwise Ikiform's hosted thank-you page at
/f/<formId>/thanks. When the form has allowed domains and the post came from anhttps:page on one of them, the page links back to it.
Rules for _redirect
A _redirect value comes from the visitor's browser, so Ikiform only follows it when it is an https: URL with no username, password or custom port, on one of the form's allowed domains or a subdomain of one. Forms without allowed domains ignore _redirect entirely. With allowed domains set to example.com:
| _redirect | Followed | Why |
|---|---|---|
https://example.com/thanks | Yes | Allowed domain |
https://app.example.com/done | Yes | Subdomain of an allowed domain |
http://example.com/thanks | No | Not https: |
https://example.com:8443/thanks | No | Custom port |
https://user@example.com/ | No | Contains a username |
https://example.com.evil.dev/ | No | Not the domain or a subdomain of it |
A _redirect that isn't followed falls through to the hosted thank-you page; it never causes an error. JSON requests ignore redirects and get 201 with the submission id.
When a post is rejected
If a post is rejected, for example because a required field is missing, the browser sees a hosted error page with the HTTP status of the error. It lists the problems by field label and links back to the form. See Errors for every code.
Field names and arrays
Repeated names and names ending in [] become arrays. Use [] for checkbox groups and <select multiple> so that a single choice is still stored as an array.
<fieldset>
<legend>Topics</legend>
<label><input type="checkbox" name="topics[]" value="billing" /> Billing</label>
<label><input type="checkbox" name="topics[]" value="api" /> API</label>
</fieldset>
<select name="plan">
<option value="starter">Starter</option>
<option value="team">Team</option>
</select>
<label><input type="checkbox" name="newsletter" /> Send me product news</label>Checking both topics and the newsletter box stores:
{
"topics": ["billing", "api"],
"plan": "team",
"newsletter": "on"
}- Names in brackets such as
address[city]are kept literally; they are not turned into nested objects. Post JSON if you want nested values. - An unchecked checkbox is not sent at all. With a
checkboxfield defined, a missing box is stored asfalseand a checked one astrue. - A submission can have up to 100 fields, names up to 128 characters and values up to 16 KB. See Limits.
422 files_not_supported.Reserved fields
Names that start with _ are instructions for Ikiform. They are never stored as answers.
| Field | Effect |
|---|---|
_gotcha | Honeypot. Leave it empty and hidden; a filled-in value adds 5 to the spam score, which is enough to store the post as spam. The name can be changed per form. |
_redirect, _next | Where to send the browser after a successful post. See Rules for _redirect. |
_subject | Subject for notification emails. Whitespace is collapsed and it is cut to 200 characters. |
_replyto | Reply-to address for notification emails. Ignored unless it is one valid email address. |
_idempotency | Idempotency key, the same as the Idempotency-Key header. See Idempotency. |
_ikiform_ts | Signed render time, set by the SDK and the shadcn/ui component. A post sent under 2 seconds after render scores as spam. |
cf-turnstile-response | The Turnstile token, added by the Turnstile widget or the SDK. |
_ikiform_via | Set by Ikiform's own human check page. Don't send it yourself. |
Any other field starting with _ is also dropped, so you can use names like _source for your own hidden markers without storing them.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<input name="email" type="email" required />
<input type="hidden" name="_subject" value="New demo request" />
<input type="hidden" name="_redirect" value="https://example.com/thanks" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Request a demo</button>
</form>The honeypot
The default honeypot name is _gotcha. You can change it per form under Honeypot field in the form's settings; whatever name you pick is treated as reserved and never stored. A plain hidden attribute is enough for most bots. If you see spam getting through, hide the input off-screen instead, because some bots skip inputs marked hidden:
<div aria-hidden="true" style="position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden">
<label>
Leave this empty
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" />
</label>
</div>Keep tabindex="-1" and autocomplete="off" so keyboard users and password managers never fill it in.
Adding a human check
Forms with the spam check set to Ikiform need no change in your HTML. When a browser posts without a token, Ikiform answers with a short "Checking you're human" page on ikiform.com. It runs Cloudflare Turnstile, then posts the original fields again with the token. Your visitor ends on the same thank-you page as before. See Spam protection for the full flow.
With your own Turnstile keys
If the form uses Your own Turnstile keys, render the widget yourself. It adds the cf-turnstile-response field to the post. Posts without a valid token are rejected with 403 captcha_failed.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<input name="email" type="email" required />
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button>Send</button>
</form>