Form ID

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

contact.html
<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>
  • method must be post. The browser sends application/x-www-form-urlencoded, or multipart/form-data if you set enctype. Both work.
  • Every control with a name becomes 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 required and type="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:

  1. The form's Redirect after submit setting in the dashboard. Any http: or https: URL works here, because only workspace members can set it.
  2. A _redirect (or _next) field in the post, if it passes the rules below.
  3. Otherwise Ikiform's hosted thank-you page at /f/<formId>/thanks. When the form has allowed domains and the post came from an https: 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:

_redirectFollowedWhy
https://example.com/thanksYesAllowed domain
https://app.example.com/doneYesSubdomain of an allowed domain
http://example.com/thanksNoNot https:
https://example.com:8443/thanksNoCustom port
https://user@example.com/NoContains a username
https://example.com.evil.dev/NoNot 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.

fields.html
<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:

Stored fields
{
  "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 checkbox field defined, a missing box is stored as false and a checked one as true.
  • A submission can have up to 100 fields, names up to 128 characters and values up to 16 KB. See Limits.
File uploads are not built yet. An empty file input is ignored; a post that contains a real file is refused with 422 files_not_supported.

Reserved fields

Names that start with _ are instructions for Ikiform. They are never stored as answers.

FieldEffect
_gotchaHoneypot. 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, _nextWhere to send the browser after a successful post. See Rules for _redirect.
_subjectSubject for notification emails. Whitespace is collapsed and it is cut to 200 characters.
_replytoReply-to address for notification emails. Ignored unless it is one valid email address.
_idempotencyIdempotency key, the same as the Idempotency-Key header. See Idempotency.
_ikiform_tsSigned render time, set by the SDK and the shadcn/ui component. A post sent under 2 seconds after render scores as spam.
cf-turnstile-responseThe Turnstile token, added by the Turnstile widget or the SDK.
_ikiform_viaSet 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.

demo.html
<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:

honeypot.html
<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.

turnstile.html
<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>