Form endpoints guide
Keep the form you already have on your website or in your app. Send it to a Formcraft endpoint and every submission is stored, emailed to you and shown in your dashboard. Nothing to design here and nothing to install.
What an endpoint is
An endpoint is a web address that accepts form submissions. It looks like this:
https://forms.example.com/e/YOUR_ID
Anything that can send an HTML form or an HTTP request can use it: a plain HTML page, a React or Vue app, a WordPress site, an Android or iOS app, or a server script. Each field you send is saved with the name you gave it.
Quick start
- Sign in and open Endpoints. Create one, give it a name and choose who gets the emails.
- Copy its URL.
- Paste the URL into your form's
action(or yourfetchcall) and set the method toPOST. - Make sure each input has a
name. Send a test. It appears under the endpoint's Submissions and in your inbox.
Plain HTML
Set action to your endpoint URL and method to POST. After sending, the visitor sees a thank-you page, or the page you choose (see After sending).
<form action="https://forms.example.com/e/YOUR_ID" method="POST">
<label>Name <input type="text" name="name" required></label>
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message"></textarea></label>
<!-- Spam trap: keep it hidden and empty -->
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>
Checkboxes with the same name are kept together as a list. Name them interests or interests[]; both work.
JavaScript (no page reload)
Send JSON and ask for JSON back with the Accept header. You get { "ok": true, "id": "…" } when it's saved.
const res = await fetch("https://forms.example.com/e/YOUR_ID", {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify({ name: "Asha", email: "asha@example.com", message: "Hello" })
});
const result = await res.json();
if (!res.ok) alert(result.error); // a readable message, see "Responses and errors"
To send an existing form element without reloading the page:
form.addEventListener("submit", async (e) => {
e.preventDefault();
const res = await fetch(form.action, { method: "POST", body: new FormData(form), headers: { Accept: "application/json" } });
const result = await res.json();
form.innerHTML = res.ok ? "<p>Thanks! We got it.</p>" : `<p>${result.error}</p>`;
});
React, Vue and others
Use the same fetch call from your submit handler. A React example:
function Contact() {
const [sent, setSent] = React.useState(false);
async function onSubmit(e) {
e.preventDefault();
const res = await fetch("https://forms.example.com/e/YOUR_ID", {
method: "POST", body: new FormData(e.target), headers: { Accept: "application/json" }
});
if (res.ok) setSent(true);
}
return sent ? <p>Thanks!</p> : (
<form onSubmit={onSubmit}>
<input name="email" type="email" required />
<textarea name="message" />
<button>Send</button>
</form>
);
}
The endpoint accepts requests from any website by default, so no server-side code is needed. To limit which websites can send to it, see Allowed websites.
WordPress, Webflow, Wix and other site builders
- Webflow: select the form, open Form Settings, set Action to your endpoint URL and Method to POST.
- WordPress: add a Custom HTML block with the HTML form above, or set your form plugin's custom action URL to the endpoint.
- Any builder with an "Embed HTML" block: paste the HTML form from this guide.
Mobile apps and servers
Send a normal HTTPS POST with JSON or form data.
# cURL curl -X POST https://forms.example.com/e/YOUR_ID \ -H "Content-Type: application/json" -H "Accept: application/json" \ -d '{"name":"Asha","email":"asha@example.com","plan":"Pro"}'
// Kotlin (OkHttp) val body = FormBody.Builder().add("name", "Asha").add("email", "asha@example.com").build() val req = Request.Builder().url("https://forms.example.com/e/YOUR_ID").header("Accept", "application/json").post(body).build() client.newCall(req).execute() // Swift var req = URLRequest(url: URL(string: "https://forms.example.com/e/YOUR_ID")!) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.setValue("application/json", forHTTPHeaderField: "Accept") req.httpBody = try JSONSerialization.data(withJSONObject: ["name": "Asha", "email": "asha@example.com"]) let (data, _) = try await URLSession.shared.data(for: req)
// PHP $ch = curl_init("https://forms.example.com/e/YOUR_ID"); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Accept: application/json"], CURLOPT_POSTFIELDS => ["name" => "Asha", "email" => "asha@example.com"]]); $result = json_decode(curl_exec($ch), true);
File uploads
Add enctype="multipart/form-data" to the form and a file input. Up to 10 files per submission. The size limit per file depends on your plan (see Limits).
<form action="https://forms.example.com/e/YOUR_ID" method="POST" enctype="multipart/form-data">
<input name="name" required>
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<button>Apply</button>
</form>
From JavaScript, send a FormData object (don't set Content-Type yourself; the browser adds it). Files can be downloaded from the dashboard. The email you receive lists the file name and size.
Special fields
Fields whose names start with an underscore change how the submission is handled. They are not saved as answers.
| Field | What it does |
|---|---|
_gotcha | Spam trap. Hide it from people. If it has any value, we reply as if all went well but store and email nothing. |
_next | Where to send the visitor after a plain HTML post, e.g. https://yoursite.in/thanks. |
_subject | Subject line of the email you receive. |
_replyto | When you reply to our email, your reply goes to this address. If you don't send it, we use a field called email. |
cf-turnstile-response | Captcha token, added for you by the captcha widget (see Captcha). |
After sending
For plain HTML posts, the visitor is taken to, in this order: the _next field, the thank-you page set in the endpoint's Settings, or our simple thank-you page. Requests that ask for JSON never redirect.
Responses and errors
With Accept: application/json you get JSON back. Every error has a readable error message you can show to people, and a code for your program.
| Status | code | Meaning |
|---|---|---|
| 200 | Saved. { "ok": true, "id": "sub_…" } | |
| 400 | validation_failed | The submission was empty. |
| 400 | captcha_failed | The captcha is switched on and wasn't completed. |
| 403 | domain_not_allowed | The endpoint only accepts submissions from certain websites. |
| 403 | quota_exceeded | This month's submission or file limit has been reached. |
| 403 | plan_required / endpoint_paused | The endpoint isn't taking submissions right now. |
| 404 | not_found | Wrong endpoint ID, or the endpoint was deleted. |
| 413 | file_too_large | A file is bigger than the plan allows. |
| 429 | rate_limited | Too many submissions from one network in a minute. Wait and try again. |
Spam
Add the hidden _gotcha field shown above. Bots fill every field they find; people never see it. Submissions from one network are also limited to a few per minute.
Allowed websites
In the endpoint's Settings, list the websites that may send to it, like yoursite.in. Subdomains such as www.yoursite.in are included. Submissions from anywhere else are refused. Leave it empty to accept from anywhere (needed for mobile apps and servers).
Captcha
For extra protection, switch on Require a captcha in the endpoint's Settings and add the widget to your form:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="SITE_KEY"></div>
The widget adds the cf-turnstile-response field to your form. If you send JSON, read the token from the widget and include it in the body under that name.
Emails to you
Each submission is emailed to the addresses chosen in the endpoint's Settings. An address must be confirmed (we send it a link) before it can receive submissions. Emails count towards your plan's monthly email allowance.
Seeing your data
- Open Endpoints in the dashboard to see every submission, open one in full and download its files.
- Download CSV gives you everything in a spreadsheet.
- Give teammates a viewer login (Account → Team) so they can see and download submissions without changing anything.
- Your data stays yours: if your plan ends, the endpoint stops taking new submissions, but everything already received stays available to view and download.
Limits
| Loading plans… |
Monthly allowances reset every month from the start of your term, also on quarterly and yearly plans.
Questions
Do I need an API key?
No. The endpoint ID in the URL is all you need. It's safe to put in your website's HTML.
Can I change my form later?
Yes. Add, rename or remove fields whenever you like. New field names simply appear as new columns.
Will my visitors see Formcraft?
Only on the thank-you page, and only if you don't set your own with _next or in Settings. JSON requests show nothing.
Can one endpoint take several forms?
Yes, but it's easier to read your data with one endpoint per form. Endpoints are unlimited.
Support
Email support with your endpoint ID, the time you sent the submission and any error message.