# How to migrate from Formspree without losing submissions

Moving a form off Formspree? Export the old submissions first, keep your field names, and leave the old form on for a day so no message is lost.

Author: Formgong
Published: 2026-10-31
Language: en
Canonical: https://formgong.com/en/blog/migrate-from-formspree/

Export your Formspree submissions before you change anything, then point the form's action at the new backend and keep every field name as it is. Leave the Formspree form switched on for a day, because cached pages and open tabs still post to the old address. Formgong reads Formspree's email, subject, _replyto, _next and _gotcha fields unchanged and imports the CSV export that Formspree offers on its paid plans.

## Where submissions get lost

Changing a form's `action` takes a minute. The messages you lose go missing around that minute, in four places:

- **The history.** Formspree keeps your old submissions. Delete the form or the account first and they are gone.

- **Old copies of the page.** A CDN cache, a preview deploy, or a visitor who opened the page an hour ago still posts to Formspree.

- **JavaScript that expects Formspree's answer.** Code that reads `next` or `errors` can show an error, or a thank-you, for a message that went somewhere else.

- **A captcha nobody checks.** A reCAPTCHA widget left on the page still makes visitors click, while the new backend ignores the result.

The steps below deal with each of them. If you have not picked a new backend yet, start with [Formspree alternatives](/en/formspree-alternative/), [Formgong vs Formspree](/en/vs/formspree/) or a head-to-head such as [Formspree vs Web3Forms](/en/compare/formspree-vs-web3forms/). If the reason is where the data is stored, see [EU Formspree alternatives](/en/blog/eu-gdpr-formspree-alternatives/). If the form is broken right now, [Formspree's error messages explained](/en/blog/formspree-errors/) may fix it without moving at all.

## Export the old submissions first

In Formspree, open the form's Submissions tab and click Export at the top right of the table. You get a choice of CSV or JSON. Formspree's help page lists the export on its Personal, Professional and Business plans (checked 31 October 2026). On the free plan there is no export button, so the notification emails you already received are your copy.

Formgong can take that CSV. In your new form's menu, open **Import submissions**, choose Formspree as the source and upload the file. You pick the date column, tick the columns to keep, see a preview, and confirm. What to know before you start:

- **100 rows per file, up to 512 KiB.** Split a longer export into several files. Importing the same rows twice is safe: repeated rows are skipped.

- **Dates need a time zone.** The date column has to look like `2026-10-02T12:00:00Z`. Up to six decimals are fine, so Formspree's `2018-11-03T07:28:29.707632` works once it ends in `Z`; if a date has no time zone, the import tells you which row and shows the fix. A spreadsheet formula such as `=A2&"Z"` adds it to the whole column. New Formspree projects use UTC; if you changed the project's time zone, write that offset (for example `+02:00`) instead of `Z`.

- **Old messages arrive quietly.** Imported rows are saved as Closed. They send no alerts and do not count against your monthly limit. Uploaded files do not come across, only the text.

- **Your archive stays.** Imported rows are kept whatever the form's retention, on every plan, up to 10,000 per form, so a message from 2018 is still there next year. Keeping the CSV as a backup does no harm.

## Field by field: what carries over

You do not need to rename your fields. Formgong reads most of Formspree's names as they are, and drops the few that would let a stranger change where your mail goes. The [HTML guide](/en/docs/html/#switch) keeps the same list for Web3Forms and FormSubmit.

Formspree's side from its own help pages, Formgong's side from its code, both checked on 31 October 2026.
FormspreeWhat it does thereIn Formgong
`action="https://formspree.io/f/…"`The address the form posts to`action="https://formgong.com/submit"` plus a hidden `access_key` field with your form's key
`email`Sets Reply-To on the notification emailWorks under the same name. `_replyto` and `replyto` work too
`subject`Sets the email subject, and can hold `{{ name }}`Used when the form's settings have no subject of their own. `{{ … }}` is not filled in, the text arrives as written
`_subject`, `_replyto`Older names, still in many tutorialsWork as they are. `_subject` wins over the subject in settings
`_next`, or the redirect in SettingsThank-you page. The Settings option is on Personal and higher plans`_next` works, so does `_redirect`. A URL sent by the page has to be on your own site. Set it in the form's settings to send people anywhere, on every plan
`_gotcha`Honeypot. A filled one is silently ignoredWorks. A filled one goes to spam and nothing is sent. Formgong's own name is `botcheck`
`_language`Language of Formspree's reCAPTCHA and thank-you pagesSaved as an ordinary answer. Use `_lang` for Formgong's own pages and messages
reCAPTCHA (`g-recaptcha-response`)Formspree checks the tokenNot checked, and the token is saved as an answer. Remove the widget: the spam filter runs on the server
Turnstile (`cf-turnstile-response`)Formspree checks it with your secret keyWorks with the same keys: paste your site key and secret key into the form's settings and turn on "Require Cloudflare Turnstile"
Extra recipientsSet in Formspree's dashboardUp to 3 addresses in the form's settings on Free. A `_cc` field from the page is ignored on purpose
`Accept: application/json`Asks for a JSON answer instead of a pageSame header, same effect
JSON `{"next": …}` or `{"errors": […]}`What AJAX code reads`{"success": true, "id": …, "message": …}` or `{"success": false, "code": …, "message": …}`. The HTTP status works for both: 200 when it worked, an error status when not
`@formspree/react``useForm("id")` posts to formspree.io and keeps `submitting`, `succeeded` and `errors`No package needed. The short hook below has the same shape
`@formspree/ajax` and `data-fs-*`Sends without a reload and shows the messagesThe attributes do nothing once the script is gone. Use a plain form, or fetch as below
Export to CSV or JSONPersonal, Professional and Business plansImport a CSV, 100 rows and 512 KiB per file, on every plan. JSON is not read

## A plain HTML form, before and after

Two lines change: the `action`, and a new hidden `access_key` field. The reCAPTCHA script and its `div` come out. Your `email`, `subject` and `_gotcha` fields stay as they are.

Without JavaScript, Formgong shows its own thank-you page after a message is sent. To send people to your page instead, add `_redirect` with an address on your own site, or set any address in the form's settings. More on that in [thank-you pages after a form](/en/blog/thank-you-page-after-form-submission/).

```html
<!-- The reCAPTCHA script and the g-recaptcha div are gone. -->
<!-- Changed: the action. New: the access_key line. Optional: _redirect. -->
<form action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label>Name <input name="name" required></label>
  <label>Email <input type="email" name="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <input type="hidden" name="subject" value="New message from the website">
  <input type="hidden" name="_redirect" value="https://www.example.com/thanks">
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <button type="submit">Send</button>
</form>
```

**Before: the same form on Formspree**

```html
<script src="https://www.google.com/recaptcha/api.js" async defer></script>

<form action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
  <label>Name <input name="name" required></label>
  <label>Email <input type="email" name="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <input type="hidden" name="subject" value="New message from the website">
  <input type="text" name="_gotcha" style="display:none">
  <div class="g-recaptcha" data-sitekey="your_site_key"></div>
  <button type="submit">Send</button>
</form>
```

## If you used @formspree/react

`@formspree/react` only talks to Formspree. Its `useForm("YOUR_FORM_ID")` posts to `formspree.io/f/…` with `Accept: application/json` and reads a `next` link on success or an `errors` list on failure. Pointing it at another address does not work, so the package has to go.

You do not need a new package to replace it. The hook below returns the same `[state, handleSubmit]` pair, so your JSX hardly changes: `state.submitting` and `state.succeeded` work as before, and `<ValidationError>` becomes one line that shows `state.error`. Run `npm uninstall @formspree/react` afterwards. For a full component with labels, react-hook-form or TypeScript, copy one from the [React](/en/for/react/) or [Next.js](/en/for/next/) page.

```jsx
"use client"; // Next.js App Router only. Delete this line in Vite or Create React App.
import { useState } from "react";

const ACCESS_KEY = "fk_your_access_key"; // the key of your Formgong form

// Same shape as Formspree's useForm: [state, handleSubmit].
function useForm() {
  const [state, setState] = useState({ submitting: false, succeeded: false, error: "" });
  async function handleSubmit(event) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    body.set("access_key", ACCESS_KEY);
    setState({ submitting: true, succeeded: false, error: "" });
    try {
      const response = await fetch("https://formgong.com/submit", {
        method: "POST",
        headers: { Accept: "application/json" },
        body,
      });
      const result = await response.json();
      const ok = response.ok && result.success === true;
      setState({ submitting: false, succeeded: ok, error: ok ? "" : result.message || "Could not send. Please try again." });
    } catch {
      setState({ submitting: false, succeeded: false, error: "Could not send. Please try again." });
    }
  }
  return [state, handleSubmit];
}

export default function ContactForm() {
  const [state, handleSubmit] = useForm();
  if (state.succeeded) return <p>Thanks, we got your message.</p>;
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required />
      <textarea name="message" required />
      <input type="text" name="_gotcha" style={{ display: "none" }} />
      <button type="submit" disabled={state.submitting}>Send</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}
```

**Before: the same form with @formspree/react**

```jsx
import { useForm, ValidationError } from "@formspree/react";

export default function ContactForm() {
  const [state, handleSubmit] = useForm("YOUR_FORM_ID");
  if (state.succeeded) return <p>Thanks, we got your message.</p>;
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required />
      <ValidationError field="email" prefix="Email" errors={state.errors} />
      <textarea name="message" required />
      <input type="text" name="_gotcha" style={{ display: "none" }} />
      <button type="submit" disabled={state.submitting}>Send</button>
      <ValidationError errors={state.errors} />
    </form>
  );
}
```

## If you used @formspree/ajax or your own fetch

With `@formspree/ajax`, the `data-fs-success`, `data-fs-error` and `data-fs-submit-btn` attributes stop doing anything once you remove its script. Either let the form post normally (the HTML above), or replace the script with a short fetch like the one in the next section.

If you wrote your own fetch, keep the `Accept: application/json` header and check `response.ok`: that part works the same on both services. What changes is the body. Formgong answers `{"success": true}` with an `id` and a `message`, or `{"success": false}` with a `code` and a `message` you can show the visitor. Code that waits for `data.next` or loops over `data.errors` needs that one line changed.

## Honeypot and captcha

Your `_gotcha` field keeps working. A bot that fills it gets a normal answer, the message goes to spam, and nothing is sent to you. How honeypots catch bots, and which ones they miss, is in [the honeypot field guide](/en/blog/honeypot-form-field/).

reCAPTCHA is the one thing to remove, not move. Formgong does not check reCAPTCHA tokens. If the widget stays, visitors still tick the box and the long token lands in every submission as an answer. Formgong filters spam on the server without a widget. If you want one anyway, it supports Cloudflare Turnstile with your own keys on every plan, and if you already used Turnstile with Formspree, the same site key and secret key work. [Turnstile, reCAPTCHA and hCaptcha compared](/en/blog/turnstile-vs-recaptcha-vs-hcaptcha/) covers the differences.

## Test, then run both for a day

Before you tell anyone, send yourself one message through the live page and check it from both ends:

- It is in the Formgong inbox, with every field you expected.

- Pressing Reply on the notification email answers the visitor, not you.

- The subject and the thank-you page are the ones you set.

- A message with the hidden `_gotcha` field filled in (type into it from the browser's developer tools) lands in spam.

The free [form checker](/en/tools/form-checker/) reads the live page and flags a missing or placeholder `access_key`, a common slip when the key is pasted by hand.

Then leave the Formspree form switched on for a day. Messages from cached pages and old tabs keep arriving there, and you can still answer them. If your form sends with JavaScript, you can go one step further and send every message to both services for that day, as below, and compare the two inboxes in the evening. Remove the Formspree line the next day. Only delete the Formspree form when nothing new has reached it for a while, and after the export.

```javascript
// For one day only: every message goes to both services.
// The visitor's result comes from Formgong. Delete the Formspree line after the test day.
// Expects <form id="contact-form"> with a <p role="status"></p> inside it.
const form = document.querySelector("#contact-form");
const status = form.querySelector("[role=status]");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const toFormgong = new FormData(form);
  toFormgong.set("access_key", "fk_your_access_key");
  const send = (url, body) => fetch(url, { method: "POST", headers: { Accept: "application/json" }, body });
  const [formgong] = await Promise.allSettled([
    send("https://formgong.com/submit", toFormgong),
    send("https://formspree.io/f/YOUR_FORM_ID", new FormData(form)), // remove after the test day
  ]);
  const ok = formgong.status === "fulfilled" && formgong.value.ok;
  status.textContent = ok ? "Thanks, we got your message." : "Could not send. Please try again.";
  if (ok) form.reset();
});
```

## Did your move go differently?

If a Formspree field, an export file or a library behaved differently from what is written here, write to [support@formgong.com](mailto:support@formgong.com). We read every message and fix the article when we are wrong.

## What Formgong's free plan includes

- **Build the form or bring your own.** The [form builder](/en/tools/form-builder/) takes the colours and fonts from your site's address, with no sign-up; ready [form templates](/en/templates/) copy into your account; AI writes the fields from one sentence. Any hand-written HTML form works too.

- **Websites with the form already connected.** 28 free [website templates](/en/website-templates/) post to Formgong out of the box.

- **250 submissions a month on unlimited forms.** Spam does not count. Once a year, a busy month keeps delivering on credit until the 7th instead of stopping.

- **Everywhere you work.** Email to up to 3 people at once, [Telegram](/en/integrations/telegram/) with Reply and Handled buttons, [Google Sheets](/en/integrations/google-sheets/), [Notion](/en/integrations/notion/), and 3 signed [webhooks](/en/integrations/webhooks/) for Slack, Discord, Zapier, Make or n8n.

- **Emails to your customers in your business's name.** Formgong sends them under your business name and replies come back to you, with no email service of your own to set up: a review request to every lead, a reminder a visitor asked for, and review emails after an order with your logo and colours. People who agree can also go straight into your [Mailchimp, Klaviyo, Brevo, MailerLite or Kit](/en/docs/marketing-lists/) list. An automatic reply to each visitor is on Pro.

- **No captcha needed.** A server-side spam filter, a honeypot, allowed domains and a block list; add your own Turnstile key if you want one.

- **Files and voice.** Visitors can attach files, or record a [voice message](/en/docs/voice-field/) that arrives transcribed.

- **Made for AI-built sites.** A hosted form page and a contact button; step-by-step guides for sites built with [Lovable](/en/docs/lovable/), [Bolt](/en/docs/bolt/), [v0](/en/docs/v0/), [Replit](/en/docs/replit/) and [Base44](/en/docs/base44/); an [MCP server](/en/docs/mcp/) lets Cursor or Claude wire the form for you.

- **See what the form does.** [Analytics](/en/docs/analytics/) for views, starts, conversion and where each lead came from, and an alert when a form goes quiet.

- **One account for the rest.** Customer reviews and an [AI help chat](/en/docs/help-chat/) that answers visitors from your own pages, with every message in one inbox. Data is stored in the EU.

## Frequently asked questions

### Can I export submissions on Formspree's free plan?

Formspree's help page lists the CSV and JSON export on its Personal, Professional and Business plans, not Free (checked 31 October 2026). On Free, the notification emails you received are your copy of past messages.

### Do I have to rename my form fields when I leave Formspree?

No. Formgong reads email, subject, _replyto, _subject, _next and _gotcha as they are. Only the form's action changes, plus one hidden access_key field.

### Will imported Formspree submissions keep their original dates?

Yes, as long as the date column has a time zone, such as 2026-10-02T12:00:00Z; up to six decimals are fine. Imported rows are kept whatever the form's retention, on every plan, up to 10,000 per form.

### Can I keep using @formspree/react with another backend?

No. The package only posts to Formspree. Replace useForm with a short fetch hook that returns the same state and handleSubmit pair, then uninstall the package.

### Does Formgong check reCAPTCHA?

No. Remove the reCAPTCHA widget when you switch. Formgong filters spam on the server and supports Cloudflare Turnstile with your own keys if you want a widget.

## Sources and documentation

- [Formspree: Exporting submissions](https://help.formspree.io/articles/form-and-project-settings/exporting-submissions)
- [Formspree: Email Reply To address](https://help.formspree.io/articles/building-your-form/email-reply-to-address)
- [Formspree: Email subject line](https://help.formspree.io/articles/building-your-form/email-subject-line)
- [Formspree: Honeypot spam filtering](https://help.formspree.io/articles/building-your-form/honeypot-spam-filtering)
- [Formspree: Thank You redirect](https://help.formspree.io/articles/form-and-project-settings/thank-you-redirect)
- [Formspree: Submit forms with JavaScript (AJAX)](https://help.formspree.io/articles/building-your-form/submit-forms-with-javascript-ajax)
- [Formspree: The Formspree React library](https://help.formspree.io/articles/working-with-react/the-formspree-react-library)
- [Formspree: Adding a custom reCAPTCHA v2 key](https://help.formspree.io/articles/form-and-project-settings/adding-a-custom-recaptcha-v2-key)
- [Formspree: Cloudflare Turnstile](https://help.formspree.io/articles/form-and-project-settings/protecting-your-forms-with-cloudflare-turnstile)
- [Formspree: Form Submissions API](https://help.formspree.io/articles/the-forms-api/form-submissions-api)
- [Formspree: Localization and translation](https://help.formspree.io/articles/building-your-form/localization-and-translation)
- [formspree-js source](https://github.com/formspree/formspree-js)
- [Formgong HTML guide](/docs/html)
- [Formgong HTML guide](https://formgong.com/en/docs/html/)

[Get a form key](https://formgong.com/en/#top)
