> ## 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.

# Webflow

> A Webflow form sends its submissions to formcarry through the Form block's Custom action setting, which posts to any URL.

Formcarry stores each submission and sends the notification email. The visitor lands on formcarry's thank you page or stays on the page with Webflow's **Success** message after you add the script on this 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. Open the form's settings

Select the Form block on the canvas or in the Navigator and open the **Settings panel**.

## 2. Remove Webflow and Email notifications

If **Webflow** or **Email notifications** are listed under **Send to**, click the **delete** icon next to each. Webflow does not combine either with a custom action.

## 3. Add the custom action

Click the **add** icon next to **Send to** and select **Custom action**. Paste the endpoint into the **Action** field:

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

Set the **Method** to **POST** rather than **GET**, otherwise formcarry answers with a page that says GET is not allowed. Click **Save**.

## 4. Publish the site

Click **Publish**. Submissions reach formcarry once the site is published.

## Field names

Every input has a **Name**: select the input and open the **Settings panel**. Formcarry stores the value under that name.

Name the visitor's address field `email` rather than `contact` or `your address`, otherwise replying to a notification goes nowhere and the auto response is not sent. Set its **Text type** to email. The default Form block's Name and Email address inputs have a **Name** too; check both.

A **Name** with spaces is sent with hyphens in their place, so `Phone Number` arrives as `Phone-Number`. A radio button group is sent under its **Group name** with the chosen **Choice value**. A select is sent under its **Name** with the choice's **Value**, not its **Text**.

## After the visitor submits

With a custom action, Webflow does not store the submission and does not send its own notification email, so **Site settings** > **Forms** stays empty for this form. The submission is on the form's page in the dashboard, and the notification email comes from formcarry.

Webflow's script does not handle a form with a custom action, so the browser posts it itself. The visitor lands on formcarry's thank you page, or on the redirect URL when you set one in the dashboard, and Webflow's **Success** and **Error** messages never show. To show them and keep the visitor on the page, add the script in the next section.

## How do I keep the visitor on the page?

Custom code needs a Core, Growth, Agency or Freelancer Workspace. Alternatively, it needs a site with an active Site plan.

Webflow renders the Form block as a `w-form` wrapper holding the form, the **Success** message (`w-form-done`), and the **Error** message (`w-form-fail`). This means a script can find all three without IDs. To post in the background and show them, open the page's **Page settings**, paste this under **Custom code** in the **Before `</body>` tag** field, and publish again:

```html theme={null}
<script>
  document.querySelectorAll('form[action^="https://formcarry.com/s/"]').forEach((form) => {
    const block = form.closest(".w-form")
    const done = block.querySelector(".w-form-done")
    const fail = block.querySelector(".w-form-fail")

    form.addEventListener("submit", async (e) => {
      e.preventDefault()
      const body = new FormData(form)
      let data = {}
      try {
        const res = await fetch(form.action, {
          method: "POST",
          headers: { "Accept": "application/json" },
          body,
        })
        data = await res.json()
      } catch (err) {}

      const stored = data.code === 200
      if (stored && form.dataset.redirect) return location.assign(form.dataset.redirect)
      form.style.display = stored ? "none" : ""
      done.style.display = stored ? "block" : "none"
      fail.style.display = stored ? "none" : "block"
      if (data.message && !stored) (fail.firstElementChild || fail).textContent = data.message
    })
  })
</script>
```

The script applies to every form on the page whose action is a formcarry endpoint.

* A stored submission hides the form and shows the **Success** message, or sends the visitor to the redirect set in the form's **Settings panel**.
* A refused submission shows the **Error** message with formcarry's reason, such as the 15 second wait. Drop the last line to keep Webflow's own Error text.

<Warning>
  Keep the **Custom action** set rather than removing it once the script runs, otherwise the script finds no form and Webflow handles the submission itself.
</Warning>

To send a value the visitor does not type, append it to the body before the request:

```js theme={null}
const body = new FormData(form)
body.append("page", location.pathname)
```

The value is stored under `page` like any other field.

A `422` carries `errors`, one entry per failing field, each with a `message`. To show each message on its field, add this at the end of the `submit` handler:

```js theme={null}
if (data.code === 422) {
  for (const name in data.errors) {
    const field = form.elements[name]
    field.setCustomValidity(data.errors[name].message)
    field.addEventListener("input", () => field.setCustomValidity(""), { once: true })
  }
  form.reportValidity()
}
```

The browser reports each message on its field, and the **Error** message shows as well.

For every page of the site, paste the script under **Site settings** > **Custom code** > **Footer code** instead.

## Spam blocker

Leave Webflow's reCAPTCHA element out of this form.

[Webflow's docs](https://help.webflow.com/hc/en-us) say it does not work as expected once a custom form action is set, and formcarry reads only its own token field, `g-recaptcha-response`. **reCAPTCHA validation** in **Site settings** > **Apps & Integrations** applies to every form on the site, so test this form again after turning it on for another form.

For a honeypot, add an **Input** to the form, set its **Name** to `_gotcha`, hide it with `display: none` in the **Style panel**, and add a custom attribute `tabindex` with the value `-1`. A submission that fills it is marked as spam.

## Test it

Fill in the form on the published site and submit it once. The submission is on the form's page in the dashboard, and the notification email arrives at your account's address.

A second test within 15 seconds from the same address gets a `429`, so wait 15 seconds before sending again.

## 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)
