# Adicione um formulário de feedback ao seu site ou app

> Coloque um formulário de feedback em qualquer página ou dentro do seu app: uma nota, um comentário, a página de origem, filtro de spam e alertas para notas baixas.

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/feedback-form

Adicione um formulário curto com uma nota de 1 a 5, um comentário opcional e um e-mail opcional, e envie a um backend de formulários para não precisar rodar um servidor para isso. Mande o endereço da página junto com cada resposta, porque o navegador não informa a URL completa por conta própria. Encaminhe as notas baixas para o chat da equipe no mesmo dia e guarde todas as respostas em um só lugar para ver tendências.

A maioria das pessoas nunca conta o que está errado no seu site. Elas simplesmente vão embora. Um pequeno formulário de feedback pega as poucas que vão falar, no momento em que percebem, na página que causou o problema. Este guia monta um que serve para qualquer site ou app, envia a página de origem, mantém os bots longe e coloca as respostas insatisfeitas na frente da sua equipe no mesmo dia.

## Como funciona um formulário de feedback

O formulário envia ao Ikiform. O Ikiform valida a nota, verifica se o envio é spam, armazena, avisa sua equipe por e-mail e envia um webhook assinado. A partir do webhook, as notas baixas vão para um canal de chat e todas as respostas podem ir para uma planilha ou banco de dados.

Diagrama: Como um feedback vai da sua página até a sua equipe

1. Um visitante escolhe uma nota, talvez escreva um comentário, e o formulário envia tudo com o endereço da página.
2. O Ikiform confere se a nota é um número inteiro de 1 a 5, roda as verificações de spam e armazena a resposta.
3. A resposta aparece no painel, onde você pode filtrá-la e exportá-la como CSV.
4. O Ikiform envia um e-mail para a sua equipe. Quando o visitante deixou um e-mail, o Reply (responder) vai direto para ele.
5. O Ikiform envia um webhook submission.created assinado com os campos e os metadados do navegador.
6. Um pequeno relay posta só as notas baixas no chat da equipe, para que sejam lidas no mesmo dia.

O relay é opcional. Comece com o e-mail e o painel, e adicione o alerta no chat quando as respostas começarem a se acumular.

## Formas de coletar feedback

Cada tipo de ferramenta foi feito para um trabalho diferente. Escolha pelo que você precisa ver quando chega uma resposta, não pela aparência do widget.

| Abordagem | Bom em | Contras |
| --- | --- | --- |
| Ferramentas de feedback para sites, como Hotjar ou Usersnap | Capturas de tela, anotações e contexto da página sem código além de uma tag script. | Um script de terceiros em todas as páginas, respostas armazenadas no produto deles e preço definido pelos planos deles. |
| O widget de um rastreador de erros, como o Sentry User Feedback | Liga um relato ao erro e, com o session replay ligado, a uma gravação do que aconteceu. | Feito para bugs, não para opiniões. Você precisa do SDK do rastreador no seu app primeiro. |
| Criadores de formulários, como [Typeform](https://ikiform.com/alternatives/typeform), [Tally](https://ikiform.com/alternatives/tally) ou [Google Forms](https://ikiform.com/alternatives/google-forms) | Formulários caprichados sem código. Bom para uma pesquisa que você manda por link. | Um iframe ou uma página separada, então a resposta perde a página a que se refere, a menos que você a passe na URL. |
| Quadros de feedback, como o Canny | Votação pública e atualizações de status em pedidos de funcionalidades. | Feito para ideias, não para reações rápidas. As respostas são públicas por definição. |
| Sua própria rota de API e banco de dados | Controle total sobre os dados e qualquer lógica. | Você mesmo constrói filtro de spam, limites de taxa, e-mail de notificação e exportação, e precisa de um servidor. |
| Um backend de formulários, como o Ikiform | Seu próprio markup em qualquer site ou app. Validação, filtro de spam, armazenamento, e-mail e webhooks assinados sem servidor. | Sem capturas de tela, porque upload de arquivos não é suportado, e sem votação nem session replay. |

Se você precisa de capturas de tela em todo relato, uma ferramenta de feedback ou um rastreador de erros justifica o script. Se você quer que o formulário tenha a cara do seu site, manter as respostas portáteis e decidir você mesmo para onde elas vão, use um backend de formulários. O resto deste guia faz isso.

## O formulário

Três perguntas bastam: uma nota, um comentário e um e-mail opcional. A nota é a que as pessoas respondem; o comentário é onde está o valor; o e-mail é como você fecha o ciclo. Escolha sua stack abaixo; a escolha vale para todos os exemplos de código destes guias.

**HTML**

Um bloco de markup e um script curto. Sem JavaScript, o formulário ainda envia e redireciona; com ele, o visitante fica na página e vê o erro do servidor ao lado do botão. O script preenche `page` com o endereço sem a query string, para que tokens em URLs nunca cheguem à sua caixa de entrada.

`feedback.html`:

```html
<form id="feedback" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <fieldset>
    <legend>How useful was this page?</legend>
    <label><input type="radio" name="rating" value="1" required /> 1</label>
    <label><input type="radio" name="rating" value="2" /> 2</label>
    <label><input type="radio" name="rating" value="3" /> 3</label>
    <label><input type="radio" name="rating" value="4" /> 4</label>
    <label><input type="radio" name="rating" value="5" /> 5</label>
  </fieldset>
  <label for="comment">What would make it better?</label>
  <textarea id="comment" name="comment" rows="4" maxlength="4000"></textarea>
  <label for="feedback-email">Email, if you'd like a reply</label>
  <input id="feedback-email" name="email" type="email" />
  <input type="hidden" name="page" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send feedback</button>
  <p id="feedback-status" role="status"></p>
</form>

<script>
  const form = document.getElementById("feedback");
  const status = document.getElementById("feedback-status");
  form.elements.page.value = location.origin + location.pathname;

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const button = form.querySelector("button");
    button.disabled = true;
    status.textContent = "Sending";
    try {
      const response = await fetch(form.action, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(form),
      });
      const result = await response.json();
      if (result.ok) {
        form.replaceChildren(Object.assign(document.createElement("p"), { textContent: "Thanks. The team reads every note." }));
        return;
      }
      const fields = result.error.fields ?? {};
      status.textContent = fields.rating?.[0] ?? fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message;
    } catch {
      status.textContent = "Couldn't send it. Try again in a moment.";
    }
    button.disabled = false;
  });
</script>
```

**Next.js**

A server action lê o header `Referer` do seu próprio envio. O formulário e a action compartilham a mesma origem, então o navegador manda a URL completa da página, e a action guarda só a origem e o caminho. Nenhum input oculto é necessário.

`app/feedback/actions.ts`:

```ts
"use server";

import { headers } from "next/headers";

export interface FeedbackState {
  sent: boolean;
  error: string;
}

export async function sendFeedback(_previous: FeedbackState, formData: FormData): Promise<FeedbackState> {
  const rating = Number(formData.get("rating"));
  if (!Number.isInteger(rating) || rating < 1 || rating > 5) return { sent: false, error: "Pick a score from 1 to 5." };

  const incoming = await headers();
  const referer = incoming.get("referer");
  const origin = incoming.get("origin");
  const page = referer ? new URL(referer) : null;

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({
      rating,
      comment: String(formData.get("comment") ?? ""),
      email: String(formData.get("email") ?? ""),
      page: page ? page.origin + page.pathname : "",
      _gotcha: String(formData.get("_gotcha") ?? ""),
    }),
  });
  const result = await response.json().catch(() => null);
  if (result?.ok) return { sent: true, error: "" };
  const fields = result?.error?.fields ?? {};
  return { sent: false, error: fields.comment?.[0] ?? fields.email?.[0] ?? "Couldn't send it. Try again." };
}
```

`app/feedback/feedback-form.tsx`:

```tsx
"use client";

import { useActionState } from "react";
import { type FeedbackState, sendFeedback } from "./actions";

const initial: FeedbackState = { sent: false, error: "" };

export function FeedbackForm() {
  const [state, action, pending] = useActionState(sendFeedback, initial);

  if (state.sent) return <p role="status">Thanks. The team reads every note.</p>;

  return (
    <form action={action}>
      <fieldset>
        <legend>How useful was this page?</legend>
        {[1, 2, 3, 4, 5].map((score) => (
          <label key={score}>
            <input type="radio" name="rating" value={score} required /> {score}
          </label>
        ))}
      </fieldset>
      <label htmlFor="comment">What would make it better?</label>
      <textarea id="comment" name="comment" rows={4} maxLength={4000} />
      <label htmlFor="feedback-email">Email, if you'd like a reply</label>
      <input id="feedback-email" name="email" type="email" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {state.error ? <p role="alert">{state.error}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send feedback"}</button>
    </form>
  );
}
```

Quer ir além com Next.js? Leia [Crie um formulário de feedback dentro do app em Next.js](https://ikiform.com/pt-br/guides/feedback-form/nextjs.md).

**Astro**

A página conhece o próprio endereço enquanto renderiza, então `Astro.url` preenche o input oculto `page` no servidor. Inputs vazios chegam à action como `null`, que o Ikiform trata como ausente. A página precisa ser renderizada sob demanda para o resultado da action aparecer.

`src/actions/index.ts`:

```ts
import { ActionError, defineAction } from "astro:actions";
import { getSecret } from "astro:env/server";
import { z } from "astro/zod";

export const server = {
  sendFeedback: defineAction({
    accept: "form",
    input: z.object({
      rating: z.coerce.number().int().min(1).max(5),
      comment: z.string().max(4000).nullish(),
      email: z.email().nullish(),
      page: z.string().nullish(),
      _gotcha: z.string().nullish(),
    }),
    handler: async (input, context) => {
      const origin = context.request.headers.get("origin");
      const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          Authorization: `Bearer ${getSecret("IKIFORM_SECRET_KEY")}`,
          ...(origin ? { Origin: origin } : {}),
        },
        body: JSON.stringify(input),
      });
      const result = await response.json();
      if (!result.ok) throw new ActionError({ code: "BAD_REQUEST", message: result.error.message });
      return { sent: true };
    },
  }),
};
```

`src/components/FeedbackForm.astro`:

```astro
---
import { actions, isInputError } from "astro:actions";

const result = Astro.getActionResult(actions.sendFeedback);
const page = Astro.url.origin + Astro.url.pathname;
---

{result?.data?.sent ? (
  <p role="status">Thanks. The team reads every note.</p>
) : (
  <form method="POST" action={actions.sendFeedback}>
    <fieldset>
      <legend>How useful was this page?</legend>
      {[1, 2, 3, 4, 5].map((score) => (
        <label><input type="radio" name="rating" value={score} required /> {score}</label>
      ))}
    </fieldset>
    <label for="comment">What would make it better?</label>
    <textarea id="comment" name="comment" rows="4" maxlength="4000"></textarea>
    <label for="feedback-email">Email, if you'd like a reply</label>
    <input id="feedback-email" name="email" type="email" />
    <input type="hidden" name="page" value={page} />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {result?.error && (
      <p role="alert">{isInputError(result.error) ? "Pick a score and check your email address." : result.error.message}</p>
    )}
    <button>Send feedback</button>
  </form>
)}
```

Quer ir além com Astro? Leia [Crie um formulário de feedback dentro do app em Astro](https://ikiform.com/pt-br/guides/feedback-form/astro.md).

**TanStack Start**

Uma server function POST confere a nota antes de qualquer coisa sair do seu servidor. O componente define `page` a partir de `window.location` no momento do envio, então ela fica correta mesmo depois de navegação no lado do cliente.

`src/components/FeedbackForm.tsx`:

```tsx
import { createServerFn, useServerFn } from "@tanstack/react-start";
import { getRequestHeader } from "@tanstack/react-start/server";
import { type FormEvent, useState } from "react";

const sendFeedback = createServerFn({ method: "POST" })
  .validator((data: unknown) => {
    if (!(data instanceof FormData)) throw new Error("Expected form data");
    const rating = Number(data.get("rating"));
    if (!Number.isInteger(rating) || rating < 1 || rating > 5) throw new Error("Pick a score from 1 to 5.");
    return {
      rating,
      comment: String(data.get("comment") ?? ""),
      email: String(data.get("email") ?? ""),
      page: String(data.get("page") ?? ""),
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
  })
  .handler(async ({ data }) => {
    const origin = getRequestHeader("origin");
    const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
        ...(origin ? { Origin: origin } : {}),
      },
      body: JSON.stringify(data),
    });
    const result = await response.json();
    if (result.ok) return { sent: true, error: "" };
    const fields = result.error.fields ?? {};
    return { sent: false, error: String(fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message) };
  });

export function FeedbackForm() {
  const send = useServerFn(sendFeedback);
  const [state, setState] = useState({ sent: false, error: "" });
  const [pending, setPending] = useState(false);

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    data.set("page", window.location.origin + window.location.pathname);
    setPending(true);
    try {
      setState(await send({ data }));
    } catch (error) {
      setState({ sent: false, error: error instanceof Error ? error.message : "Couldn't send it. Try again." });
    } finally {
      setPending(false);
    }
  }

  if (state.sent) return <p role="status">Thanks. The team reads every note.</p>;

  return (
    <form method="post" onSubmit={onSubmit}>
      <fieldset>
        <legend>How useful was this page?</legend>
        {[1, 2, 3, 4, 5].map((score) => (
          <label key={score}>
            <input type="radio" name="rating" value={score} required /> {score}
          </label>
        ))}
      </fieldset>
      <label htmlFor="comment">What would make it better?</label>
      <textarea id="comment" name="comment" rows={4} maxLength={4000} />
      <label htmlFor="feedback-email">Email, if you'd like a reply</label>
      <input id="feedback-email" name="email" type="email" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {state.error ? <p role="alert">{state.error}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send feedback"}</button>
    </form>
  );
}
```

Quer ir além com TanStack Start? Leia [Crie um formulário de feedback dentro do app em TanStack Start](https://ikiform.com/pt-br/guides/feedback-form/tanstack-start.md).

**React Router**

Uma resource route guarda a action, e `useFetcher` envia para ela de qualquer página sem navegação. Registre a rota como `api/feedback` em `app/routes.ts`. `useLocation` fornece o caminho, e a action o resolve em relação à própria URL, que tem a origem do seu site.

`app/routes/api.feedback.ts`:

```ts
import { data } from "react-router";
import type { Route } from "./+types/api.feedback";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const origin = request.headers.get("origin");
  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({
      rating: String(formData.get("rating") ?? ""),
      comment: String(formData.get("comment") ?? ""),
      email: String(formData.get("email") ?? ""),
      page: new URL(String(formData.get("path") ?? "/"), request.url).href,
      _gotcha: String(formData.get("_gotcha") ?? ""),
    }),
  });
  const result = await response.json();
  if (result.ok) return { sent: true, error: "" };
  const fields = result.error.fields ?? {};
  return data(
    { sent: false, error: String(fields.rating?.[0] ?? fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message) },
    { status: response.status },
  );
}
```

`app/components/feedback-form.tsx`:

```tsx
import { useFetcher, useLocation } from "react-router";
import type { action } from "../routes/api.feedback";

export function FeedbackForm() {
  const fetcher = useFetcher<typeof action>();
  const { pathname } = useLocation();
  const pending = fetcher.state !== "idle";

  if (fetcher.data?.sent) return <p role="status">Thanks. The team reads every note.</p>;

  return (
    <fetcher.Form method="post" action="/api/feedback">
      <fieldset>
        <legend>How useful was this page?</legend>
        {[1, 2, 3, 4, 5].map((score) => (
          <label key={score}>
            <input type="radio" name="rating" value={score} required /> {score}
          </label>
        ))}
      </fieldset>
      <label htmlFor="comment">What would make it better?</label>
      <textarea id="comment" name="comment" rows={4} maxLength={4000} />
      <label htmlFor="feedback-email">Email, if you'd like a reply</label>
      <input id="feedback-email" name="email" type="email" />
      <input type="hidden" name="path" value={pathname} />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {fetcher.data?.error ? <p role="alert">{fetcher.data.error}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send feedback"}</button>
    </fetcher.Form>
  );
}
```

Quer ir além com React Router? Leia [Crie um formulário de feedback dentro do app com React Router](https://ikiform.com/pt-br/guides/feedback-form/react-router.md).

**SvelteKit**

Um endpoint `+server.ts` encaminha o envio, então o componente pode ficar num layout e funcionar em todas as páginas. `page` de `$app/state` dá a URL atual. Declare `IKIFORM_SECRET_KEY` em `src/env.ts` com `defineEnvVars` para que `$app/env/private` consiga lê-la. O endpoint devolve `Response.json()`, já que o SvelteKit 3 descontinua o helper `json()`.

`src/routes/api/feedback/+server.ts`:

```ts
import { error } from "@sveltejs/kit";
import { IKIFORM_SECRET_KEY } from "$app/env/private";
import type { RequestHandler } from "./$types";

export const POST: RequestHandler = async ({ request, fetch, url }) => {
  const body = await request.json();
  const rating = Number(body.rating);
  if (!Number.isInteger(rating) || rating < 1 || rating > 5) error(400, "Pick a score from 1 to 5.");

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${IKIFORM_SECRET_KEY}`,
      Origin: request.headers.get("origin") ?? url.origin,
    },
    body: JSON.stringify({
      rating,
      comment: String(body.comment ?? ""),
      email: String(body.email ?? ""),
      page: String(body.page ?? ""),
      _gotcha: String(body._gotcha ?? ""),
    }),
  });
  const result = await response.json();
  if (!result.ok) {
    const fields = result.error.fields ?? {};
    error(response.status, String(fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message));
  }
  return Response.json({ sent: true });
};
```

`src/lib/FeedbackForm.svelte`:

```svelte
<script lang="ts">
  import { page } from "$app/state";

  let rating = $state(0);
  let comment = $state("");
  let email = $state("");
  let trap = $state("");
  let status = $state<"idle" | "sending" | "sent">("idle");
  let message = $state("");

  async function send(event: SubmitEvent) {
    event.preventDefault();
    status = "sending";
    message = "";
    const response = await fetch("/api/feedback", {
      method: "POST",
      headers: { Accept: "application/json", "Content-Type": "application/json" },
      body: JSON.stringify({ rating, comment, email, page: page.url.origin + page.url.pathname, _gotcha: trap }),
    });
    if (response.ok) {
      status = "sent";
      return;
    }
    message = (await response.json().catch(() => null))?.message ?? "Couldn't send it. Try again.";
    status = "idle";
  }
</script>

{#if status === "sent"}
  <p role="status">Thanks. The team reads every note.</p>
{:else}
  <form onsubmit={send}>
    <fieldset>
      <legend>How useful was this page?</legend>
      {#each [1, 2, 3, 4, 5] as score}
        <label><input type="radio" name="rating" value={score} bind:group={rating} required /> {score}</label>
      {/each}
    </fieldset>
    <label for="comment">What would make it better?</label>
    <textarea id="comment" name="comment" rows="4" maxlength="4000" bind:value={comment}></textarea>
    <label for="feedback-email">Email, if you'd like a reply</label>
    <input id="feedback-email" name="email" type="email" bind:value={email} />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden bind:value={trap} />
    {#if message}
      <p role="alert">{message}</p>
    {/if}
    <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send feedback"}</button>
  </form>
{/if}
```

Quer ir além com SvelteKit? Leia [Crie um formulário de feedback dentro do app em SvelteKit](https://ikiform.com/pt-br/guides/feedback-form/sveltekit.md).

**Nuxt**

Uma server route guarda a chave em `runtimeConfig.ikiformSecretKey`, preenchida a partir de `NUXT_IKIFORM_SECRET_KEY`. `useRoute().path` e `useRequestURL()` montam o endereço da página tanto no servidor quanto no navegador.

`server/api/feedback.post.ts`:

```ts
export default defineEventHandler(async (event) => {
  const body = await readBody<{ rating?: number; comment?: string; email?: string; page?: string; _gotcha?: string }>(event);
  const config = useRuntimeConfig(event);
  const origin = getRequestHeader(event, "origin");

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${config.ikiformSecretKey}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({
      rating: body.rating,
      comment: body.comment ?? "",
      email: body.email ?? "",
      page: body.page ?? "",
      _gotcha: body._gotcha ?? "",
    }),
  });
  const result = await response.json();
  if (!result.ok) {
    const fields = result.error.fields ?? {};
    throw createError({
      status: response.status,
      message: String(fields.rating?.[0] ?? fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message),
    });
  }
  return { sent: true };
});
```

`app/components/FeedbackForm.vue`:

```vue
<script setup lang="ts">
const route = useRoute();
const requestUrl = useRequestURL();
const rating = ref<number | null>(null);
const comment = ref("");
const email = ref("");
const trap = ref("");
const status = ref<"idle" | "sending" | "sent">("idle");
const error = ref("");

async function send() {
  status.value = "sending";
  error.value = "";
  try {
    await $fetch("/api/feedback", {
      method: "POST",
      body: {
        rating: rating.value,
        comment: comment.value,
        email: email.value,
        page: requestUrl.origin + route.path,
        _gotcha: trap.value,
      },
    });
    status.value = "sent";
  } catch (failure) {
    error.value = (failure as { data?: { message?: string } }).data?.message ?? "Couldn't send it. Try again.";
    status.value = "idle";
  }
}
</script>

<template>
  <p v-if="status === 'sent'" role="status">Thanks. The team reads every note.</p>
  <form v-else @submit.prevent="send">
    <fieldset>
      <legend>How useful was this page?</legend>
      <label v-for="score in 5" :key="score">
        <input v-model="rating" type="radio" name="rating" :value="score" required /> {{ score }}
      </label>
    </fieldset>
    <label for="comment">What would make it better?</label>
    <textarea id="comment" v-model="comment" name="comment" rows="4" maxlength="4000" />
    <label for="feedback-email">Email, if you'd like a reply</label>
    <input id="feedback-email" v-model="email" name="email" type="email" />
    <input v-model="trap" name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error" role="alert">{{ error }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Send feedback" }}</button>
  </form>
</template>
```

Quer ir além com Nuxt? Leia [Crie um formulário de feedback dentro do app em Nuxt](https://ikiform.com/pt-br/guides/feedback-form/nuxt.md).

**React**

Sem servidor próprio, o navegador envia direto ao Ikiform. `submit()` de `@ikiform/sdk` manda o `FormData` e devolve `{ ok }` ou os erros de campo. Nunca coloque uma chave secreta aqui; quem protege o formulário é **Allowed domains**.

`src/FeedbackForm.tsx`:

```tsx
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function FeedbackForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    data.set("page", window.location.origin + window.location.pathname);
    setStatus("sending");
    setError("");
    const result = await submit(FORM_ID, data);
    if (result.ok) {
      setStatus("sent");
      return;
    }
    const fields = result.error.fields ?? {};
    setError(fields.rating?.[0] ?? fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Thanks. The team reads every note.</p>;

  return (
    <form onSubmit={onSubmit}>
      <fieldset>
        <legend>How useful was this page?</legend>
        {[1, 2, 3, 4, 5].map((score) => (
          <label key={score}>
            <input type="radio" name="rating" value={score} required /> {score}
          </label>
        ))}
      </fieldset>
      <label htmlFor="comment">What would make it better?</label>
      <textarea id="comment" name="comment" rows={4} maxLength={4000} />
      <label htmlFor="feedback-email">Email, if you'd like a reply</label>
      <input id="feedback-email" name="email" type="email" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send feedback"}</button>
    </form>
  );
}
```

**Vue**

O mesmo envio pelo navegador num single-file component. O rótulo do comentário muda quando a nota é 1 ou 2, o que rende respostas mais específicas de visitantes insatisfeitos.

`src/components/FeedbackForm.vue`:

```vue
<script setup lang="ts">
import { ref } from "vue";

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";

const rating = ref(0);
const status = ref<"idle" | "sending" | "sent">("idle");
const error = ref("");

async function send(event: SubmitEvent) {
  const body = new FormData(event.target as HTMLFormElement);
  body.set("page", window.location.origin + window.location.pathname);
  status.value = "sending";
  error.value = "";
  try {
    const response = await fetch(ENDPOINT, { method: "POST", headers: { Accept: "application/json" }, body });
    const result = await response.json();
    if (result.ok) {
      status.value = "sent";
      return;
    }
    const fields = result.error.fields ?? {};
    error.value = fields.rating?.[0] ?? fields.comment?.[0] ?? fields.email?.[0] ?? result.error.message;
  } catch {
    error.value = "Couldn't send it. Try again in a moment.";
  }
  status.value = "idle";
}
</script>

<template>
  <p v-if="status === 'sent'" role="status">Thanks. The team reads every note.</p>
  <form v-else :action="ENDPOINT" method="post" @submit.prevent="send">
    <fieldset>
      <legend>How useful was this page?</legend>
      <label v-for="score in 5" :key="score">
        <input v-model="rating" type="radio" name="rating" :value="score" required /> {{ score }}
      </label>
    </fieldset>
    <label for="comment">{{ rating && rating <= 2 ? "What went wrong?" : "What would make it better?" }}</label>
    <textarea id="comment" name="comment" rows="4" maxlength="4000" />
    <label for="feedback-email">Email, if you'd like a reply</label>
    <input id="feedback-email" name="email" type="email" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error" role="alert">{{ error }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Send feedback" }}</button>
  </form>
</template>
```

- Troque `frm_xxxxxxxxxxxxxxxx` pelo id do seu formulário, que está no painel.
- `rating` é um grupo de radio com cinco valores. O Ikiform o armazena como número, então médias não exigem parsing.
- `email` é opcional. As pessoas escrevem com mais honestidade quando não precisam se identificar, e quem quer uma resposta vai deixá-lo.
- O input oculto `_gotcha` é o honeypot. As pessoas nunca o veem; bots que preenchem todos os inputs se marcam como spam.
- `page` registra a qual página a resposta se refere. A próxima seção explica por que você mesmo a envia.

> As versões no servidor enviam com uma chave secreta, então o Ikiform pula **Allowed domains** e a verificação humana. Todo envio passa a vir do IP do seu servidor, e o limite Standard de 5 por minuto por visitante conta todos os seus visitantes juntos. Escolha o [limite de taxa](https://ikiform.com/pt-br/docs/rate-limits.md) Relaxed ou Custom para um formulário que você envia por um servidor, e mantenha `IKIFORM_SECRET_KEY` em variáveis de ambiente só do servidor.

Os guias de frameworks na documentação cobrem a configuração com mais detalhes: [Next.js](https://ikiform.com/pt-br/docs/guides/nextjs.md), [Astro](https://ikiform.com/pt-br/docs/guides/astro.md), [SvelteKit](https://ikiform.com/pt-br/docs/guides/sveltekit.md), [Nuxt](https://ikiform.com/pt-br/docs/guides/nuxt.md), [React](https://ikiform.com/pt-br/docs/guides/react.md), [Vue](https://ikiform.com/pt-br/docs/guides/vue.md) e [HTML puro](https://ikiform.com/pt-br/docs/html.md). O [modelo de formulário de feedback](https://ikiform.com/forms/feedback) gera os mesmos campos em HTML, React e server action; os modelos de [relato de bug](https://ikiform.com/forms/bug-report) e de [pesquisa](https://ikiform.com/forms/survey) cobrem os formulários mais longos.

## Defina os campos

Cole uma definição na aba **Fields** do formulário e o Ikiform a aplica em todo envio, de qualquer página. Teste abaixo: a prévia roda o validador do Ikiform no seu navegador, então uma nota `0` ou um e-mail malformado mostra o `422` que você receberia de volta.

Formulário: Page feedback

- **Quão útil foi esta página?** (`rating`, rating, obrigatório)
- **O que a deixaria melhor?** (`comment`, textarea)
- **E-mail, se quiser uma resposta** (`email`, email)
- **Página** (`page`, url)

Um `rating` aceita um número inteiro de 1 até o seu `max`, aqui 5. Ele pode ir até 10, mas cinco opções são mais rápidas de responder.

| Campo | Tipo | Por quê |
| --- | --- | --- |
| `rating` | `rating`, máx. 5 | Um clique. Armazenado como número, então dá para tirar a média por página |
| `comment` | `textarea`, até 4.000 caracteres | O motivo por trás da nota |
| `email` | `email`, opcional | Responder a quem faz uma pergunta. Vira o reply-to da notificação |
| `page` | `url`, opcional | A qual página a resposta se refere |

Com campos definidos, qualquer outro nome no envio é recusado como `Unknown field`. Se depois você mandar mais contexto, como uma versão do app ou o nome de um plano, adicione um campo para ele antes. Ids de campo usam snake_case em minúsculas.

## Saiba a qual página se refere

Uma nota sem página é difícil de usar. O Ikiform registra os metadados do navegador em cada envio, mas um desses valores é menos útil do que parece:

| Valor | De onde vem | O que você recebe |
| --- | --- | --- |
| `meta.user_agent` | Registrado pelo Ikiform | O navegador e o sistema operacional, de um envio pelo navegador |
| `meta.origin` | Registrado pelo Ikiform | A origem do seu site, como `https://example.com` |
| `meta.referrer` | Registrado pelo Ikiform | Normalmente só a origem. Os navegadores usam por padrão a política `strict-origin-when-cross-origin`, que remove o caminho ao enviar para outro site |
| `meta.country` | Registrado pelo Ikiform | Um código de país de duas letras |
| `page` | Enviado pelo seu formulário | O caminho completo, para você saber a qual página a resposta se refere |

É por isso que todos os exemplos acima enviam `page` por conta própria. As versões HTML e SPA leem `location`; as versões de servidor leem o endereço que o próprio framework já conhece. Todas removem a query string, que pode conter termos de busca, ids de rastreamento ou tokens de uso único que você não quer armazenar.

> As versões de servidor enviam a partir do seu servidor, então `meta.user_agent` é o runtime do seu servidor, não o navegador do visitante. Para feedback sobre páginas isso raramente importa. Para relatos de bug importa, e o [guia de relato de bug](https://ikiform.com/pt-br/guides/feedback-form/bug-report.md) encaminha o user agent do navegador como um campo.

## Onde perguntar

O lugar do formulário decide o que você ouve. Um formulário no rodapé de todas as páginas recebe um fio de opiniões fortes. Uma pergunta no fim de uma tarefa recebe respostas constantes e específicas.

| Posição | Bom para | Cuidado com |
| --- | --- | --- |
| Fim de uma página de documentação ou artigo | Encontrar páginas que não respondem à pergunta | Pouco volume por página. Leia por seção, não por página |
| Depois de uma tarefa no seu app, como uma exportação ou o primeiro projeto | Como um fluxo específico é percebido, enquanto ainda está fresco | Pergunte uma vez por pessoa, não toda vez que ela terminar a tarefa |
| Um botão flutuante em todas as telas | Qualquer coisa, a qualquer hora. Veja o [widget em React](https://ikiform.com/pt-br/guides/feedback-form/react-feedback-widget.md) | Ele não pode cobrir seus próprios botões, banners de cookies ou bolhas de chat |
| Um link num e-mail | Notas que você quer acompanhar ao longo do tempo, como o [NPS](https://ikiform.com/pt-br/guides/feedback-form/nps-survey.md) | O cliente de e-mail não é a sua página, então o formulário precisa de uma landing page no seu site |

Mantenha o formulário pequeno onde quer que ele fique. Peça a nota primeiro e mostre a caixa de comentário junto, não numa segunda etapa. Uma segunda etapa perde a maioria das pessoas, e uma nota sem motivo ainda vale a pena.

## Coloque as notas baixas na frente das pessoas

Diagrama: O que acontece com uma resposta conforme a nota

1. Toda resposta é armazenada, seja qual for a nota e a página de origem.
2. O Ikiform envia um webhook submission.created assinado para o seu relay.
3. O relay lê a nota. Notas de 3 a 5 não precisam de mais nada de imediato.
4. Uma nota 1 ou 2 vai para o chat da equipe com a página e o comentário.
5. Se o visitante deixou um e-mail, alguém responde no mesmo dia.
6. Uma vez por semana, a exportação mostra as médias por página, que é onde as tendências aparecem.

Alerte só sobre as notas ruins. Um canal que apita a cada 5 de 5 é silenciado em uma semana.

Os e-mails de notificação vão para até 10 destinatários confirmados por formulário e chegam a cada resposta. Isso funciona para algumas respostas por dia. Acima disso, mande as notas baixas para o chat com um relay como esta rota do Next.js; o mesmo código roda num Cloudflare Worker:

`app/api/feedback-alerts/route.ts`:

```ts
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";

export async function POST(request: Request) {
  const payload = await request.text();
  try {
    const event = await verifyWebhook({
      payload,
      headers: request.headers,
      secret: process.env.IKIFORM_WEBHOOK_SECRET!,
    });
    if (event.type !== "submission.created") return new Response(null, { status: 204 });
    const { rating, comment, page, email } = event.data.fields;
    if (Number(rating) > 2) return new Response(null, { status: 204 });

    const lines = [
      `*${rating}/5* on ${page ?? event.data.meta.origin ?? "an unknown page"}`,
      comment ? `> ${String(comment).slice(0, 500)}` : "_No comment._",
      email ? `Reply to ${email}` : "Anonymous",
    ];
    const response = await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ text: lines.join("\n") }),
    });
    if (!response.ok) return new Response(`Slack answered ${response.status}`, { status: 502 });
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
    throw error;
  }
}
```

- `verifyWebhook` confere a assinatura e o timestamp, para que ninguém mais consiga postar no seu canal por esta URL.
- Notas acima de 2 respondem `204` e param. O Ikiform trata qualquer 2xx como entregue.
- Uma chamada ao Slack que falha responde `502`, e o Ikiform tenta de novo com o mesmo `webhook-id`, então uma queda curta não faz perder um alerta.
- Os valores dos campos são o que o visitante digitou. O relay corta o comentário em 500 caracteres antes de chegar ao chat.

Adicione o endpoint em **Webhooks** e copie o secret `whsec_` dele para `IKIFORM_WEBHOOK_SECRET`. Um workspace pode ter 3 endpoints. Para caminhos sem código, veja [Slack](https://ikiform.com/blog/form-submissions-to-slack.md), [Discord](https://ikiform.com/blog/form-submissions-to-discord.md) e [Google Sheets](https://ikiform.com/blog/form-submissions-to-google-sheets.md). Para feedback que deve virar um ticket, [Linear e GitHub Issues](https://ikiform.com/blog/form-submissions-to-linear-and-github-issues.md) tem um relay que cria um por envio.

> Não há integrações de um clique. Toda ferramenta é alcançada por um [webhook](https://ikiform.com/pt-br/docs/webhooks.md) assinado, diretamente ou por uma ferramenta de automação como [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) ou [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md).

## Mantenha os bots longe

Um formulário de feedback em todas as páginas é um formulário público em todas as páginas. Os bots o encontram, e quase tudo o que mandam são links. Os padrões do Ikiform cuidam da maior parte:

| Defesa | O que ela barra | Configuração |
| --- | --- | --- |
| Honeypot | Bots que preenchem todos os inputs | O input oculto `_gotcha` nos exemplos |
| Pontuação de spam | Três ou mais links (+2), frases de spam conhecidas (+2), e-mails descartáveis (+2), repetições idênticas (+3). Com 5, é armazenado como spam | Nenhuma |
| Limites de taxa | Scripts que enviam em loop | Ligado por padrão: Standard, 5 por minuto por visitante, e 300 por minuto para o formulário inteiro |
| Domínios permitidos | Envios de outros sites | Adicione seu domínio nas configurações do formulário |
| Verificação humana | Bots que rodam um navegador de verdade | Defina **Spam check** como **Ikiform**. Envios por `fetch` no navegador passam a precisar de um token de `challenge()` |

O spam recebe a mesma resposta de sucesso que um envio real, não dispara e-mail nem webhook e não conta no uso. Um visitante real que cola três links num comentário pode acionar a regra de links junto com uma repetição, então olhe o filtro **Spam** de vez em quando. Marcar uma resposta como não spam envia o webhook dela nesse momento. A [documentação de spam](https://ikiform.com/pt-br/docs/spam.md) explica cada verificação.

## Leia as respostas

Leia todos os comentários nas primeiras semanas. Depois disso, a visão útil é a nota média por página, da menor para a maior, com os comentários ao lado.

### Exportar

Abra **Submissions**, escolha o formulário de feedback, selecione um intervalo de datas e clique em **Export CSV**. As colunas são `id`, `created_at`, `form` e `status`, depois uma por campo, então com os campos acima `rating` é a coluna E e `page` é a coluna H. Uma exportação comporta até 100.000 linhas; o [guia de exportação](https://ikiform.com/pt-br/docs/guides/export.md) cobre a API para mais que isso.

Importe o arquivo no Google Sheets e coloque isto numa planilha vazia. Ela lista cada página com sua nota média e o número de respostas, da pior para a melhor:

`Google Sheets`:

```text
=QUERY(A2:H, "select H, avg(E), count(E) where D = 'received' and E is not null group by H order by avg(E) asc label avg(E) 'average', count(E) 'answers'", 0)
```

1. Ignore páginas com só uma ou duas respostas. Um visitante irritado não é uma tendência.
2. Leia os comentários das três páginas com notas mais baixas. A correção costuma estar nos primeiros.
3. Depois de mudar uma página, compare a média dela nas semanas antes e depois, não o número de todos os tempos.

> O feedback não é anônimo para o Ikiform mesmo sem e-mail: cada resposta registra o user agent, a origem, o referrer e o país, além de um hash do IP que nunca é devolvido. Diga o que você coleta no seu aviso de privacidade.

## Escolha o seu caso

O formulário acima cobre reações rápidas em qualquer página. Estas páginas cobrem feedback com outro objetivo: um bug para reproduzir, um pedido para avaliar, uma nota para acompanhar ou um cliente de saída.

- [Formulário de relato de bug](https://ikiform.com/pt-br/guides/feedback-form/bug-report.md): Um formulário de relato de bug que pede às pessoas os passos e a gravidade, e deixa a página adicionar sozinha a URL, o navegador, o viewport e os erros recentes.
- [Formulário de pedido de funcionalidade](https://ikiform.com/pt-br/guides/feedback-form/feature-request.md): Um formulário de pedido de funcionalidade que pergunta pelo problema em vez da solução, armazena pedidos por tema, conta pessoas, não linhas, e avisa quando sair.
- [Widget de feedback em React](https://ikiform.com/pt-br/guides/feedback-form/react-feedback-widget.md): Um botão de feedback flutuante para um app React que abre um pequeno painel, envia a tela, a versão do app e a conta com cada resposta e nunca perde um rascunho.
- [Feedback de beta testers](https://ikiform.com/pt-br/guides/feedback-form/beta-tester-feedback.md): Um formulário de feedback de beta com um link pessoal por tester e build, duas escalas de nota e um resumo semanal que mostra se cada build melhorou.
- [Pesquisa NPS](https://ikiform.com/pt-br/guides/feedback-form/nps-survey.md): Rode uma pesquisa NPS com a nota de 0 a 10 no e-mail, uma pergunta de acompanhamento na sua própria página que muda com a nota e um script que calcula o NPS.
- [Pesquisa de cancelamento](https://ikiform.com/pt-br/guides/feedback-form/cancellation-survey.md): Pergunte por que os clientes cancelam dentro do fluxo de cancelamento do seu SaaS, deixe o Pular a um clique, salve o motivo no Stripe e no Ikiform e nunca atrase o cancelamento.
- [Formulário de feedback em Next.js](https://ikiform.com/pt-br/guides/feedback-form/nextjs.md): Um formulário de feedback dentro do app em Next.js: uma server action que lê a sessão, pega a página da requisição e encaminha uma avaliação com o usuário anexado.
- [Formulário de feedback em Astro](https://ikiform.com/pt-br/guides/feedback-form/astro.md): Um formulário de feedback dentro do app em Astro: uma action que pega o usuário dos locals do middleware, a página da requisição e a tela do padrão da rota.
- [Formulário de feedback em TanStack Start](https://ikiform.com/pt-br/guides/feedback-form/tanstack-start.md): Um formulário de feedback dentro do app em TanStack Start: uma server function POST com middleware de sessão, o id da rota como contexto e a página lida dos headers.
- [Formulário de feedback com React Router](https://ikiform.com/pt-br/guides/feedback-form/react-router.md): Um formulário de feedback com React Router 8: a action de uma resource route lê a sessão do cookie, um fetcher envia do layout e um envio sem JS redireciona de volta.
- [Formulário de feedback em SvelteKit](https://ikiform.com/pt-br/guides/feedback-form/sveltekit.md): Um formulário de feedback dentro do app em SvelteKit: um formulário no layout envia para uma form action, que adiciona no servidor o usuário de locals e a página da requisição.
- [Formulário de feedback em Nuxt](https://ikiform.com/pt-br/guides/feedback-form/nuxt.md): Um formulário de feedback dentro do app em Nuxt: uma server route que lê a sessão do usuário, pega a página do Referer e funciona mesmo antes do JavaScript carregar.

## Confira se funciona

Repasse isto antes de colocar o formulário em todas as páginas. O Ikiform não tem plano gratuito. Testar é grátis: um workspace sem plano aceita 50 envios de teste por mês a partir de `localhost`. Para entrar no ar, escolha o Pro a partir de US$ 6/mês para 500 envios, ou pague uma vez: 10.000 envios por US$ 49 ou 50.000 envios por US$ 149, com créditos que nunca expiram. Veja os [preços](https://ikiform.com/pt-br/pricing).

1. Em **Allowed domains** do formulário, adicione o domínio do seu site, mais `localhost` enquanto desenvolve. Deixe vazio para aceitar envios de qualquer lugar.
2. Escolha uma nota, escreva um comentário e envie. A frase de agradecimento substitui o formulário, ou o navegador cai na sua página de redirecionamento na versão em HTML puro.
3. Abra **Submissions** no painel. A resposta mostra `rating` como número e `page` com o caminho completo.
4. Envie uma nota `6` com `curl` ou pelo console do navegador. O Ikiform responde `422` apontando `rating`.
5. Confira a caixa de entrada de cada destinatário das notificações. Cada um recebe um e-mail com os campos, e **Reply** vai para quem enviou quando o formulário tem um campo `email`.
6. Envie uma nota 1. Seu relay a posta no chat da equipe, e a entrega em **Webhooks** mostra `succeeded`.
7. Envie com o honeypot preenchido, `_gotcha=bot`. Ele ainda recebe uma resposta de sucesso, mas o envio é armazenado como spam e não dispara e-mail nem webhook.
8. Não chegou nada? Mude o filtro de status do painel para **Spam**. Spam recebe a mesma resposta de sucesso de um envio real.

## Solução de problemas

| Problema | O que fazer |
| --- | --- |
| `403 origin_not_allowed`, ou todo envio cai no spam | O domínio da página não está em **Allowed domains** do formulário. Adicione-o, mais `localhost` para testes locais. Subdomínios de um domínio listado são permitidos. |
| `422` com `Rating must be at most 5` | O formulário manda uma nota maior que o `max` do campo. Mude os valores do radio ou aumente o `max` na definição, até 10. |
| `422` com um Unknown field | O formulário manda um nome que não está na definição, geralmente um input oculto novo. Adicione um campo com esse id ou remova o input. |
| `page` está vazio | O script que o preenche não rodou, ou rodou antes de o formulário existir. Coloque-o depois do formulário, ou defina `page` no momento do envio como fazem os exemplos de SPA. |
| `meta.referrer` só mostra seu domínio | Esperado. Por padrão, os navegadores mandam só a origem em envios entre sites. Use o campo `page` no lugar. |
| O envio está no painel, mas nenhum e-mail ou webhook chegou | Ele foi armazenado como spam. Spam não envia nada. Abra-o no filtro **Spam** para ver os motivos e marque-o como não spam para enviar o e-mail e os webhooks dele. |
| `429 rate_limited` | Um visitante enviou mais do que o limite de taxa do formulário permite. O Standard permite 5 por minuto por visitante. Espere o `Retry-After`, ou escolha Relaxed ou Custom para formulários que enviam a partir de um único IP compartilhado. |
| `402 test_limit_reached` | O workspace não tem plano e já usou os 50 testes gratuitos de `localhost` deste mês. Escolha um plano ou use **Send test** no painel. |

## Perguntas

### Como adiciono um formulário de feedback a um site sem backend?

Aponte o action do formulário para o endpoint de um backend de formulários. O formulário pode ser HTML puro num site estático; o backend valida, armazena e avisa por e-mail sobre cada resposta, e envia um webhook se você quiser a resposta em outro lugar.

### Como sei de qual página veio o feedback?

Envie o endereço da página como um campo. O Ikiform também registra o referrer, mas os navegadores normalmente mandam só o seu domínio num envio entre sites, então um campo de página preenchido a partir de location é o jeito confiável.

### O feedback deve ser anônimo?

Deixe o e-mail opcional. As pessoas são mais sinceras sem ele, e quem quer uma resposta vai deixá-lo. O Ikiform ainda registra o navegador, a origem, o referrer e o país de cada resposta.

### Os visitantes podem anexar uma captura de tela?

Não como arquivo. O Ikiform recusa envios com arquivos com 422 files_not_supported. Peça um link para uma captura de tela, ou use uma ferramenta de feedback feita em torno de capturas se você precisar de uma em todo relato.

### Posso usar uma escala de 0 a 10 em vez de estrelas?

Não com um campo rating, que vai de 1 a no máximo 10. Uma nota de 0 a 10 usa um campo number com min 0, max 10 e integer true. O guia de NPS mostra a configuração completa.

### Como recebo alertas sobre feedback ruim?

Adicione um webhook para um pequeno relay que confere a nota e posta só as notas baixas no Slack ou no Discord. Os e-mails de notificação cobrem todas as respostas; o relay cobre as que precisam de resposta no mesmo dia.

### O Ikiform é gratuito?

Não, não há plano gratuito. Testar é grátis: você pode enviar 50 envios de teste por mês a partir de localhost antes de pagar. Para entrar no ar, o Pro começa em US$ 6/mês para 500 envios, ou você pode pagar uma vez 10.000 envios por US$ 49 ou 50.000 envios por US$ 149, com créditos que nunca expiram.

## Mais guias

- [Formulário de relato de bug](https://ikiform.com/pt-br/guides/feedback-form/bug-report.md): Um formulário de relato de bug que pede às pessoas os passos e a gravidade, e deixa a página adicionar sozinha a URL, o navegador, o viewport e os erros recentes.
- [Formulário de pedido de funcionalidade](https://ikiform.com/pt-br/guides/feedback-form/feature-request.md): Um formulário de pedido de funcionalidade que pergunta pelo problema em vez da solução, armazena pedidos por tema, conta pessoas, não linhas, e avisa quando sair.
- [Widget de feedback em React](https://ikiform.com/pt-br/guides/feedback-form/react-feedback-widget.md): Um botão de feedback flutuante para um app React que abre um pequeno painel, envia a tela, a versão do app e a conta com cada resposta e nunca perde um rascunho.
- [Feedback de beta testers](https://ikiform.com/pt-br/guides/feedback-form/beta-tester-feedback.md): Um formulário de feedback de beta com um link pessoal por tester e build, duas escalas de nota e um resumo semanal que mostra se cada build melhorou.
- [Pesquisa NPS](https://ikiform.com/pt-br/guides/feedback-form/nps-survey.md): Rode uma pesquisa NPS com a nota de 0 a 10 no e-mail, uma pergunta de acompanhamento na sua própria página que muda com a nota e um script que calcula o NPS.
- [Pesquisa de cancelamento](https://ikiform.com/pt-br/guides/feedback-form/cancellation-survey.md): Pergunte por que os clientes cancelam dentro do fluxo de cancelamento do seu SaaS, deixe o Pular a um clique, salve o motivo no Stripe e no Ikiform e nunca atrase o cancelamento.
- [Formulário de feedback em Next.js](https://ikiform.com/pt-br/guides/feedback-form/nextjs.md): Um formulário de feedback dentro do app em Next.js: uma server action que lê a sessão, pega a página da requisição e encaminha uma avaliação com o usuário anexado.
- [Formulário de feedback em Astro](https://ikiform.com/pt-br/guides/feedback-form/astro.md): Um formulário de feedback dentro do app em Astro: uma action que pega o usuário dos locals do middleware, a página da requisição e a tela do padrão da rota.
- [Formulário de feedback em TanStack Start](https://ikiform.com/pt-br/guides/feedback-form/tanstack-start.md): Um formulário de feedback dentro do app em TanStack Start: uma server function POST com middleware de sessão, o id da rota como contexto e a página lida dos headers.
- [Formulário de feedback com React Router](https://ikiform.com/pt-br/guides/feedback-form/react-router.md): Um formulário de feedback com React Router 8: a action de uma resource route lê a sessão do cookie, um fetcher envia do layout e um envio sem JS redireciona de volta.
- [Formulário de feedback em SvelteKit](https://ikiform.com/pt-br/guides/feedback-form/sveltekit.md): Um formulário de feedback dentro do app em SvelteKit: um formulário no layout envia para uma form action, que adiciona no servidor o usuário de locals e a página da requisição.
- [Formulário de feedback em Nuxt](https://ikiform.com/pt-br/guides/feedback-form/nuxt.md): Um formulário de feedback dentro do app em Nuxt: uma server route que lê a sessão do usuário, pega a página do Referer e funciona mesmo antes do JavaScript carregar.
- [Formulário de captação de leads](https://ikiform.com/pt-br/guides/lead-capture.md): Um formulário de leads no seu próprio site que envia e-mail com reply-to, avisa o chat da equipe e alimenta seu CRM, para você responder enquanto o lead ainda está lá.
- [Formulário de inscrição em newsletter](https://ikiform.com/pt-br/guides/newsletter-signup.md): Crie um formulário de newsletter no seu próprio markup, registre o consentimento, barre inscrições de bots e passe cada assinante para Kit, MailerLite, beehiiv ou qualquer ferramenta.
- [Envio de formulários HTML](https://ikiform.com/pt-br/guides/html-form-submission.md): O que o navegador faz quando um formulário é enviado: quais campos manda, como os codifica, o que o servidor deve responder e como o Ikiform responde.
