> ## Documentation Index
> Fetch the complete documentation index at: https://formcarry.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Framer

> Framer sends a form's submissions to formcarry through its Webhook destination, posting each one as JSON from its own address.

You set the input names and the endpoint in Framer; the code component at the end of this page posts from each visitor's browser instead, with the spam blocker and formcarry's thank you page.

## Prerequisites

Before you start, you need:

* A formcarry account. [Sign up](https://app.formcarry.com/register) is free.
* A form in the [dashboard](https://app.formcarry.com). Its endpoint is on the form's Setup page. The examples use `https://formcarry.com/s/AbC123xyz`; put yours in its place.

## 1. Add a form in Framer

Open the **Insert** menu, go to **Forms** and drag a form component onto your page. To add an input, select the form on the canvas, click the **+** icon in the bottom toolbar and pick **Text**, **Checkbox**, **Radio** or **Select**.

## 2. Name every input

Select an input and set its **Name** in the properties panel on the right. Framer posts the inputs as JSON keyed by that name, so it is the field name you see in the dashboard.

## 3. Send the form to your endpoint

Select the form on the canvas. In the right sidebar, click **Add…** next to **Send To** and choose **Webhook**. Enter your endpoint as the webhook URL:

```text theme={null}
https://formcarry.com/s/AbC123xyz
```

Framer signs each request with a secret if you set one here; formcarry does not check the signature.

## 4. Publish the site

Click **Publish** in the top right corner of the editor.

## Field names

A Checkbox sends `true` or `false`. Name the visitor's address input `email`: it becomes the reply to address of the notification email and the recipient of the auto response.

For a value the visitor does not see, enable **Hidden** on the input rather than turning **Visible** off, otherwise Framer leaves the input out of the submission.

## After the visitor submits

The submission comes from Framer, not from the visitor's browser. The visitor sees what you set in Framer: a redirect on submit, an overlay, or a change to the button or the form. They never see formcarry's thank you page.

Leave the form's thank you URL in the dashboard empty rather than setting one, otherwise formcarry answers Framer's post with a redirect, which Framer does not follow.

A stored submission is answered with a `200`, which Framer counts as delivered; any other answer makes Framer send the submission again.

## Spam blocker

Framer's own spam protection is on by default and runs on the visitor's device before Framer sends the submission. Framer's JSON carries no challenge token.

Leave this form's spam blocker **off** rather than pasting a secret key into Form Security, otherwise every submission is refused with a `403` (reCAPTCHA) or a `400` (hCaptcha, Turnstile).

## A code component that posts from the browser

Framer's code components are React components rendered on the published site, so a form inside one posts from the visitor's browser, as on the [React](/docs/frameworks/react) page. In the **Assets** panel select **Code**, click **Create Code File**, replace the file's contents with this component, then add it to your canvas:

```jsx theme={null}
import { useState } from "react"

export default function ContactForm() {
  const [status, setStatus] = useState("idle")
  const [message, setMessage] = useState("")

  async function submit(e) {
    e.preventDefault()
    setStatus("sending")
    const res = await fetch("https://formcarry.com/s/AbC123xyz", {
      method: "POST",
      headers: { "Accept": "application/json", "Content-Type": "application/json" },
      body: JSON.stringify(Object.fromEntries(new FormData(e.currentTarget))),
    })
    const data = await res.json()
    setMessage(data.message)
    setStatus(data.code === 200 ? "sent" : "error")
  }

  if (status === "sent") return <p>{message}</p>

  return (
    <form onSubmit={submit}>
      <input name="email" type="email" placeholder="Email" />
      <textarea name="message" placeholder="Message" />
      <input name="_gotcha" type="text" style={{ display: "none" }} tabIndex={-1} autoComplete="off" />
      <button type="submit" disabled={status === "sending"}>Send</button>
      {status === "error" && <p>{message}</p>}
    </form>
  )
}
```

The input names are the keys formcarry stores. `_gotcha` is the honeypot: leave it empty, a submission that fills it is marked as spam. A stored submission answers with `code: 200`; a refused one answers with the reason in `message`, see [What every form needs](/docs/what-every-form-needs).

Each visitor posts from their own address, so the 15 second window is theirs alone. A challenge's token is read in the browser, so the spam blocker applies to this form: send the token under the field name formcarry reads, see [Spam blocker](/docs/features/spam-protection). Give the component its own form in the dashboard rather than the Webhook form's endpoint, otherwise turning the spam blocker on refuses the Webhook submissions.

To send a file, add a file input and post the `FormData` itself, leaving the content type to the browser:

```jsx theme={null}
async function submit(e) {
  e.preventDefault()
  setStatus("sending")
  const res = await fetch("https://formcarry.com/s/AbC123xyz", {
    method: "POST",
    headers: { "Accept": "application/json" },
    body: new FormData(e.currentTarget),
  })
  const data = await res.json()
  setMessage(data.message)
  setStatus(data.code === 200 ? "sent" : "error")
}

<input name="attachment" type="file" />
```

Send `Accept` and nothing else rather than adding `Content-Type` yourself, otherwise the multipart boundary is missing and the upload fails. Files are stored on paid plans; on the free plan the rest of the submission is stored without them.

To land the visitor on formcarry's thank you page, or on the form's redirect if you set one, put `action` and `method` on the form and leave `onSubmit` off:

```jsx theme={null}
export default function ContactForm() {
  return (
    <form action="https://formcarry.com/s/AbC123xyz" method="POST">
      <input name="email" type="email" placeholder="Email" />
      <textarea name="message" placeholder="Message" />
      <button type="submit">Send</button>
    </form>
  )
}
```

The browser posts the form itself, without an `Accept` header, so the answer is the thank you page or the redirect.

## Test it

On the published page, fill the form in and submit it once. The submission is on the form's page in the [dashboard](https://app.formcarry.com), and the notification email arrives at your account's address.

## What's next

* [What every form needs](/docs/what-every-form-needs): field names, hidden inputs, limits and the answers.
* [Thank you pages](/docs/features/thank-you-pages): what the visitor sees after submitting.
* [Spam protection](/docs/features/spam-protection): the challenges, the filter and the honeypot.

Stuck? Write to [help@formcarry.com](mailto:help@formcarry.com). Include the form id.


## Related topics

- [Quickstart](/docs/quickstart.md)
- [Introduction](/docs/introduction.md)
