Migrate · Formcarry
Migrate from Formcarry to Formward
Formcarry and Formward both take a standard POST from your own HTML or a fetch() call, so most of the move is the action URL. Formcarry's docs use action URLs of the form formcarry.com/s/ followed by the form ID, a _gotcha honeypot that Formward reads under the same name, and a _next field for redirects on paid plans.
Two Formcarry conventions are worth a look: it uses the field called email as the reply-to address and the auto-response recipient (Formward also takes Reply-To from the email field), and its JSON responses carry a code and a message rather than Formward's ok and id.
Last checked 7 October 2026. What this guide says about Formcarry comes from Formcarry's own documentation, listed under Sources. Check their current docs if something looks different in your account. The Formward side is the reference in the migration docs.
The change
For a plain HTML form the action URL is the only required change. If you use _next, rename it to _redirect.
Before: Formcarry, HTML
<form action="https://formcarry.com/s/YOUR_FORM_ID" method="POST">
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<input type="hidden" name="_next" value="https://example.com/thanks">
<input type="text" name="_gotcha" style="display:none">
<button type="submit">Send</button>
</form>After: Formward, HTML
<form action="https://forms.formward.eu/f/<formId>" method="POST">
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<input type="hidden" name="_redirect" value="/thanks">
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>Before: Formcarry, fetch()
const res = await fetch("https://formcarry.com/s/YOUR_FORM_ID", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const data = await res.json();
if (data.code !== 200) throw new Error(data.message);After: Formward, fetch()
const res = await fetch("https://forms.formward.eu/f/<formId>", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);Field and feature mapping
| Formcarry | Formward | Notes |
|---|---|---|
| https://formcarry.com/s/{id} | https://forms.formward.eu/f/<formId> | The only required change for a plain HTML form. |
| _gotcha | _gotcha | Same name. Formward stores honeypot hits as spam without counting them toward your quota. |
| _next | _redirect | A relative path, or a URL on one of the form's allowed origins. The redirect set on the form is the fallback. |
| email (reply-to) | email (same) | A field named email sets Reply-To on Formward too; _replyto overrides it. |
| g-recaptcha-response, h-captcha-response | cf-turnstile-response | Formward supports Cloudflare Turnstile, optional per form. Remove reCAPTCHA or hCaptcha widgets. |
| file input, multipart | file input, multipart | Same markup. Uploads are on Professional and up (5 files of up to 10 MiB each on Professional). |
| Dashboard thank-you page | Redirect URL on the form | Set it in the form's settings, or per form with _redirect. |
What changes, what stays
Changes
- The action or fetch() URL.
- _next becomes _redirect.
- JavaScript that reads data.code, data.message or data.errors reads data.ok and data.error instead.
- reCAPTCHA or hCaptcha widgets are removed or replaced with Turnstile.
Stays the same
- Your other field names; Formward stores every named field as sent.
- The email field, which still sets Reply-To on the notification.
- The _gotcha honeypot.
- POST with URL-encoded or multipart bodies, and fetch() with Accept: application/json and FormData.
Differences to test before you switch
Specific to Formcarry
- Formcarry's docs say the redirect set in its dashboard wins over _next. In Formward it is the other way round: a valid _redirect field wins over the redirect set on the form.
- Formcarry uses the email field as the reply-to address and as the auto-response recipient. Formward takes Reply-To from the email field too, with _replyto as an override, and the autoresponder is set up on the form.
- Formcarry's JSON responses use code and message, with an errors object on validation failures. Formward returns ok, id and error, so check any code that reads the old keys.
- If you use the formcarry.js script from carrier.formcarry.com, remove it and use the fetch() call above.
How Formward behaves
- JSON responses. Send Accept: application/json and a success is a 200 with { ok: true, id }. An error is { ok: false, error } with a status code. Without that header, a successful post redirects or shows Formward's thanks page. Code that only checks res.ok keeps working.
- Status codes. 402 when the plan's monthly submission limit is reached (the body has upgrade: true), 422 when a field fails a validation rule set on the form or required consent is missing, 429 when one address posts too often (with a Retry-After header), and 403 when the origin is not allowed, the Turnstile check fails or files are sent on a plan without uploads.
- File uploads. Uploads need enctype="multipart/form-data" and a plan that includes them: Professional and up (5 files of up to 10 MiB each on Professional). On Free and Personal a post that carries files is refused with 403, so test an upload before you switch a form that takes attachments.
- Redirects. A hidden _redirect is followed only if it is a relative path or a URL on one of the form's allowed origins; otherwise it is ignored and the redirect set on the form applies, then Formward's thanks page. JSON (AJAX) requests are never redirected.
- Spam. If the _gotcha honeypot is filled in, the sender gets a normal success response, but the submission is stored as spam, sends no notification and does not count toward your quota. Rate limits apply on every plan; Cloudflare Turnstile and disposable-email blocking are optional per form.
- Allowed origins. With the list empty, any site can post to the endpoint. Once you add an origin, requests whose Origin header does not match are refused with 403, including server-side requests that send no Origin header.
- Reply-To. The Reply-To of the notification email is taken from _replyto, then _reply_to, then a field named email. So a plain email field sets Reply-To with no rename, and _replyto only overrides it, for example when the email field has another name or the form has several. An invalid address is dropped, and the email then has no Reply-To.
Every status code and body is listed in responses and redirects.
History, integrations and settings
- Formcarry's docs list CSV and JSON export of submissions on its Basic, Growth and Premium plans. Formward's CSV importer (Dashboard, Import & migrate) takes any CSV with a header row, up to 5,000 rows and 10 MB per file. Imported rows send no email and do not count toward your quota. Each row keeps its original date when the file has a column named Submitted At, created_at or createdAt; otherwise it is dated at import time. Attached files are not imported, only cell values.
- Forms on the Free plan keep submissions for 30 days, and the nightly retention sweep deletes imported rows older than a form's retention window. To keep a longer history, import into a form on a paid plan with a long enough window.
- Webhooks, Zapier connections, auto-responses and custom email templates belong to your Formcarry account and do not move with the endpoint. Recreate the ones you need: Formward webhooks (including Slack, Discord, Teams and Mattermost), Google Sheets and Baserow sync start on Professional, the autoresponder on Professional, and extra notification recipients on Personal.
Pricing and data location
- Formcarry's pricing page lists a free plan with 50 submissions a month; check it for the current paid plans.
- Formcarry says all its data is stored in the European Economic Area. It is operated by Teijal, Inc. in Delaware, USA.
- Formward is priced in euros: Free with 50 submissions a month, then Personal at EUR 15, Professional at EUR 30 and Business at EUR 90 a month (EUR 10, 20 and 60 billed yearly).
- Formward stores and processes submissions at Hostup AB in Sweden and sends notification email through Mailjet in the EU. The one optional non-EU sub-processor is Cloudflare Turnstile, on forms where you switch it on.
The full side-by-side, with sources, is in Formward vs Formcarry. Formward's prices are on the plans page, and where each part of Formward runs is on EU data residency.
Step-by-step checklist
- Create the form. In the Formward dashboard, create a form, set the notification email and copy the endpoint.
- Change the action. Replace the formcarry.com/s/ URL in the form's action, or in your fetch() call, with the Formward endpoint.
- Rename _next. Rename _next to _redirect. The visitor's email field needs no change; it still sets Reply-To on notifications.
- Update the response check. If your JavaScript reads data.code or data.errors, switch it to data.ok and data.error.
- Restrict origins. Add your site's origin, for example https://www.example.com, to the form's allowed origins. Leave the list empty if you also post from a server.
- Test before you switch. Point a copy of the form at Formward and send a few entries: a normal one, one with the honeypot filled, and one with a file if the form takes uploads. Check the dashboard and the notification email.
- Recreate integrations. Set up webhooks, the autoresponder and extra notification recipients on the Formward form.
- Bring the history. If you want past submissions, export them as CSV from your old provider and import them under Dashboard, Import & migrate.
- Retire the Formcarry form. Switch the live form, watch the first real submissions arrive, then disable the form at Formcarry so nothing is split between two inboxes.
Sources
Last checked 7 October 2026
Move one form first
The free plan takes 50 submissions a month with no card. Point a copy of one form at Formward, send a few test entries, and switch the live form once the path works end to end.