Documentation
Connect a form in two minutes
Scroll Theory Forms gives any HTML form a backend. You point your form at an endpoint, and submissions are stored, spam-filtered, and emailed to you. No JavaScript required, and nothing to install.
Quick start
Three steps:
- Create a form in your dashboard. You get a unique endpoint.
- Set your HTML form's
actionto that endpoint and itsmethodtoPOST. - Submit it. The submission appears in your dashboard, and if you have a verified notification email, it lands in your inbox.
The endpoint
Every form has an endpoint shaped like this:
POST https://forms.scrolltheory.media/f/YOUR_FORM_ID
It accepts three body types, so it works with plain HTML forms and with JavaScript:
- URL-encoded (a normal HTML form post).
- Multipart (a form with a file input; files are recorded by name in this version).
- JSON (an AJAX request, see AJAX and JSON).
HTML forms
A complete contact form. Replace YOUR_FORM_ID with the id from your dashboard.
<form action="https://forms.scrolltheory.media/f/YOUR_FORM_ID" method="POST">
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<!-- optional: set the email subject -->
<input type="hidden" name="_subject" value="New lead from your site">
<!-- spam honeypot: keep it hidden, leave it empty -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true">
<button type="submit">Send</button>
</form>
That is the whole integration. No library, no API key in the page.
Field names
Use any field names you like. Whatever you send is stored and shown in your dashboard, and emailed to you. Common names like name, email, and message work well, and a field named email is automatically used as the reply-to address on your notification unless you set one explicitly.
If you plan to use routing rules, you can declare your field names when you create the form. That lets rules reference them before the first submission arrives. You can always add more later.
Control fields
A few reserved field names change how a submission is handled instead of being stored as data. They are prefixed with an underscore and are compatible with Formspree, so an existing form keeps working.
| Field | What it does |
|---|---|
_replyto | Sets the reply-to address on your notification email. Defaults to a field named email if present. |
_subject | Sets the subject line of your notification email. |
_next | Redirects the visitor to this URL after a successful submit. Overrides the form's default redirect. |
_cc | Adds a carbon-copy address to the notification. |
_gotcha | Honeypot. Keep it hidden and empty. If a bot fills it, the submission is quarantined as spam. |
_format | Hints the response format. |
Captcha tokens (cf-turnstile-response, g-recaptcha-response, h-captcha-response) are detected automatically and never stored as form data.
Redirects and the thank-you page
After a normal form submit, one of three things happens, in this order:
- If a routing rule sets a redirect, the visitor goes there.
- Otherwise, if the submission includes
_nextor the form has a default redirect URL, the visitor goes there. - Otherwise, a clean built-in thank-you page is shown.
To send visitors to your own page, add a hidden field:
<input type="hidden" name="_next" value="https://yoursite.com/thank-you">
AJAX and JSON
For single-page apps or custom success states, post JSON and set the Accept header to application/json. The endpoint replies with JSON instead of redirecting.
const res = await fetch("https://forms.scrolltheory.media/f/YOUR_FORM_ID", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({ email, message })
});
const data = await res.json();
// { ok: true, id: 123, spam: false, limited: false }
if (data.ok) {
// show your own success state
}
A successful response looks like { ok: true, id, spam, limited }. The spam flag tells you the submission was quarantined, and limited tells you the account is over its monthly cap so no email was sent. The submission is still stored either way. A failure returns { ok: false, error } with an appropriate status code.
Spam handling
Spam is quarantined, not deleted. A flagged submission is still stored and visible in your dashboard, so a false positive never costs you a real lead. This is the core difference from filters that silently drop messages.
The checks run in order: the honeypot field, an optional Cloudflare Turnstile challenge, a rate limit on bursts from one source, and your blocklist of addresses, IPs, or keywords. Whatever trips first marks the submission as spam and stops the rest.
Notifications and verified emails
To receive email when a form is submitted, add a notification address and verify it from the Emails page. Verification exists so the service cannot be used to send mail to addresses you do not control. You choose which verified address each form notifies, and routing rules can send different submissions to different verified addresses.
Plans and limits
Every plan stores every submission. When an account goes over its monthly submission cap, email notifications pause, but submissions keep being recorded and stay available in the dashboard. Upgrading restores notifications. Current plans and limits are on the pricing section.
Moving from Formspree
The control fields above are Formspree-compatible. In most cases you change the form's action URL to your Scroll Theory Forms endpoint and the form keeps working, including _replyto, _subject, _next, and _gotcha. The difference you will notice is that flagged messages are held for review instead of disappearing.
Questions about anything here can go to chad@scrolltheory.media.