Switch from Formspree
Move a Formspree form to SilentForms by changing /f/{form_id} to your key. _replyto, _subject, _next and _gotcha keep working.
SilentForms understands Formspree-style forms: the /f/… URL shape, the underscore fields and the ok and errors keys in JSON responses. For a plain HTML form the switch is a single change to the action URL, with your SilentForms access key where the Formspree form ID was.
Step by step#
- Get a SilentForms access key. Enter your email on the access key page and click the confirmation link. The key plays the role of the Formspree form ID.
- Change the endpoint. Replace
https://formspree.io/f/your_form_idwithhttps://silentforms.com/f/followed by your key. No hidden key field is needed. - Point a Formspree library at SilentForms, if you use one. One option does it; see JavaScript and the Formspree libraries below.
- Test. Submit the form from your site and check your inbox.
- Optional: set up the dashboard. Sign in with the same email to see submissions, export CSV, restrict the form to your domain or turn on a CAPTCHA. See the dashboard.
Before and after#
A Formspree form:
<form action="https://formspree.io/f/xyzabcd" method="POST">
<input type="hidden" name="_subject" value="New contact form message">
<input type="hidden" name="_next" value="https://example.com/thanks">
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<input type="text" name="_gotcha" style="display: none;">
<button type="submit">Send</button>
</form>The same form on SilentForms. Only the action changed:
<form action="https://silentforms.com/f/YOUR_ACCESS_KEY_HERE" method="POST">
<input type="hidden" name="_subject" value="New contact form message">
<input type="hidden" name="_next" value="https://example.com/thanks">
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<input type="text" name="_gotcha" style="display: none;">
<button type="submit">Send</button>
</form>You can also post to https://silentforms.com/api/submit with the key in an accessKey field, as the rest of these docs do. Both forms are equivalent; if a key is in the URL, it wins.
Field reference#
| Formspree field | On SilentForms |
|---|---|
email | Works: the visitor's address, shown in the email and used as Reply-To. |
_replyto | Works: Reply-To address when it should differ from email. |
_subject | Works: subject line of the email you receive. |
_next | Works: where an HTML form post goes after success. Must be an absolute https:// or http:// URL. |
_gotcha | Works: honeypot. A submission with a value here gets a normal success response but is not emailed; it waits in the dashboard's spam view for 7 days. |
_cc | Accepted. Copies go to the extra recipients you add and who confirm in the dashboard; an address in the field cannot add anyone by itself. |
_format, _language, _template | Accepted and ignored. |
| File inputs | Ignored: file uploads are not supported. |
Every other named field is emailed to you in form order. Checkbox groups and <select multiple> keep every value; see Checkboxes and multi-select.
JavaScript and the Formspree libraries#
If you post with your own fetch, change the URL and keep the code. SilentForms answers JSON when the request sends Accept: application/json or a JSON body, with ok: true on success and, on failure, an HTTP error status plus an errors array whose items have a message. So code like this keeps working:
const response = await fetch('https://silentforms.com/f/YOUR_ACCESS_KEY_HERE', {
method: 'POST',
headers: { Accept: 'application/json' },
body: new FormData(form),
});
const data = await response.json();
if (data.ok) {
form.reset();
status.textContent = 'Thanks! Your message has been sent.';
} else {
status.textContent = data.errors.map((error) => error.message).join(', ');
}Formspree's errors may also carry code and field; SilentForms' errors have only message, so code that highlights a field by error.field will not find one.
The @formspree/react and @formspree/core libraries keep working too. They build the URL as an endpoint origin plus /f/ and your form key, which is exactly the SilentForms URL shape, and SilentForms answers with the ok and next fields they check for. Pass your SilentForms access key as the form key and set endpoint. In React:
import { useForm, ValidationError } from '@formspree/react';
export default function ContactForm() {
const [state, handleSubmit] = useForm('YOUR_ACCESS_KEY_HERE', { endpoint: 'https://silentforms.com' });
if (state.succeeded) return <p role="status">Thanks! Your message has been sent.</p>;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required />
<input type="text" name="_gotcha" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
<button type="submit" disabled={state.submitting}>Send</button>
<ValidationError errors={state.errors} />
</form>
);
}With @formspree/core, pass the same option to submitForm:
import { createClient } from '@formspree/core';
const result = await createClient().submitForm('YOUR_ACCESS_KEY_HERE', data, { endpoint: 'https://silentforms.com' });
if (result.kind === 'success') form.reset();Errors arrive as form-level errors with the message SilentForms returns. Field-level errors (ValidationError with a field prop) stay empty, because SilentForms does not tie an error to a field.
reCAPTCHA and other CAPTCHAs#
Formspree can run reCAPTCHA for you. On SilentForms you use your own keys: create a reCAPTCHA site in Google's admin console, add the widget to your form with its site key, then turn reCAPTCHA on for the form in the dashboard and paste the secret key. Add the widget first, or every submission is refused with "Captcha is required." Step by step: Google reCAPTCHA. Cloudflare Turnstile and hCaptcha work the same way.
Next steps#
- Every field, response and error on one page: the overview.
- A complete React form with loading and error states: React.
- Something not arriving? Troubleshooting.