3 fields
Feedback form
A short feedback form with a 1 to 5 rating and an optional comment, for product pages, docs and in-app widgets.
Preview
Runs Ikiform's validator in your browser. Nothing is sent.
Copy the code
Every version below is generated from the same field definition. Replace frm_xxxxxxxxxxxxxxxx with your form id, or enter it in the Form ID box and the code updates.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label for="rating">How useful was this?</label>
<input id="rating" name="rating" type="number" min="1" max="5" step="1" required />
<label for="comment">What would make it better?</label>
<textarea id="comment" name="comment" rows="4" maxlength="4000"></textarea>
<label for="email">Email, if you'd like a reply</label>
<input id="email" name="email" type="email" autocomplete="email" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Send feedback</button>
</form>- HTML needs no JavaScript. After a successful post the browser is redirected to the form's redirect URL, or to Ikiform's hosted thank-you page.
- React uses the SDK:
npm install @ikiform/sdk. Field errors from the server appear under each field. - JavaScript enhances the HTML form with
fetch, so the visitor stays on the page. - The hidden
_gotchainput is the honeypot. Leave it empty and hidden.
challenge(FORM_ID) from @ikiform/sdk and pass it to submit as turnstileToken, or add it to the fetch body as cf-turnstile-response. Without it the post is refused with 403 challenge_required. See the Next.js guide and Spam protection.The fields
Paste this definition on the form's Fields tab in the dashboard. Ikiform then checks every post against it on the server, refuses bad data with 422 validation_failed, refuses any field that isn't listed, and stores typed values.
| Field id | Type | Required | Accepts |
|---|---|---|---|
rating | rating | Yes | 1 to 5 |
comment | textarea | No | Up to 4,000 characters |
email | email | No | One valid address |
{
"version": 1,
"title": "Feedback",
"fields": [
{
"id": "rating",
"type": "rating",
"label": "How useful was this?",
"required": true,
"validation": {
"max": 5
}
},
{
"id": "comment",
"type": "textarea",
"label": "What would make it better?",
"rows": 4,
"validation": {
"max_length": 4000
}
},
{
"id": "email",
"type": "email",
"label": "Email, if you'd like a reply"
}
],
"settings": {
"submit_label": "Send feedback",
"success_message": "Thanks for the feedback."
}
}Next.js server action
To keep the form working without JavaScript in a Next.js app, post from a server action with a secret key. Create the key under API keys with the submissions:write scope and keep it out of the browser.
IKIFORM_SECRET_KEY=ik_sk_your_secret_key"use server";
export interface FeedbackFormState {
ok: boolean;
message: string;
fields?: Record<string, string[]>;
values?: Record<string, string[]>;
}
export async function sendFeedbackForm(_previous: FeedbackFormState, formData: FormData): Promise<FeedbackFormState> {
const body = new URLSearchParams();
for (const [name, value] of formData) {
if (typeof value === "string" && !name.startsWith("$ACTION")) body.append(name, value);
}
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
},
body,
});
const result = await response.json();
if (result.ok) return { ok: true, message: "Thanks for the feedback." };
const values = Object.fromEntries([...new Set(body.keys())].map((name) => [name, body.getAll(name)]));
return { ok: false, message: result.error.message, fields: result.error.fields, values };
}"use client";
import { useActionState } from "react";
import { type FeedbackFormState, sendFeedbackForm } from "./actions";
const initial: FeedbackFormState = { ok: false, message: "" };
export function FeedbackForm() {
const [state, action, pending] = useActionState(sendFeedbackForm, initial);
if (state.ok) return <p>{state.message}</p>;
return (
<form action={action}>
<label htmlFor="rating">How useful was this?</label>
<input id="rating" name="rating" type="number" min={1} max={5} step={1} required defaultValue={state.values?.rating?.[0]} />
{state.fields?.rating ? <p>{state.fields.rating[0]}</p> : null}
<label htmlFor="comment">What would make it better?</label>
<textarea id="comment" name="comment" rows={4} maxLength={4000} defaultValue={state.values?.comment?.[0]} />
{state.fields?.comment ? <p>{state.fields.comment[0]}</p> : null}
<label htmlFor="email">Email, if you'd like a reply</label>
<input id="email" name="email" type="email" autoComplete="email" defaultValue={state.values?.email?.[0]} />
{state.fields?.email ? <p>{state.fields.email[0]}</p> : null}
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{state.message && !state.fields ? <p role="alert">{state.message}</p> : null}
<button disabled={pending}>{pending ? "Sending" : "Send feedback"}</button>
</form>
);
}Set it up
- Create a form in the dashboard and copy its id.
- Paste the field definition on the Fields tab.
- Add your site under Allowed domains, plus
localhostwhile you develop. - Set the rate limit to Standard, 5 a minute per visitor if visitors post from the browser. Feedback is one post per visit. Standard leaves room for a second thought. With the server action, every post comes from your server's IP, so choose Relaxed or Custom instead.
- Paste the code, send a test and check it arrives in the dashboard.
The rating field
A rating accepts a whole number from 1 to max, which is 5 here and can be 2 to 10. It is stored as a number, so averages and charts need no parsing. 0, 6 and 3.5 are refused.
Knowing which page it came from
Every submission records the visitor's user agent, origin, referrer and country alongside the fields. Webhooks include them under data.meta, so you don't need hidden fields for the page or the browser.
| Metadata | Example |
|---|---|
referrer | https://example.com/docs/install |
origin | https://example.com |
user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) |
country | GB |
As a small widget
A feedback form often lives in a corner of every page. Post it with fetch or the SDK so the visitor stays on the page, and add each domain it appears on under Allowed domains. Keep the email optional: people give more honest feedback when they don't have to identify themselves.
Where to send it
Every submission can go to a signed webhook. These guides cover the tools that suit this form, with the relay code:
- Send form submissions to Slack
- Send form submissions to Discord
- Send form submissions to a Notion database
- Send form submissions to Google Sheets
- Create Linear or GitHub issues from form submissions
Questions
Not with a rating field, which starts at 1 and goes up to 10 at most. For 0 to 10, use a number field with min 0, max 10 and integer true, as the survey form does.
Ikiform records the referrer and origin with every submission and includes them in webhooks, so you don't need a hidden page field.
The fields are only what the visitor types, and email is optional here. Ikiform still records the user agent, origin, referrer and country with each submission.