Create a form
Open the app, choose Forms, then New form. Or from a terminal:POST /api/v1/forms/<app>/<name>, and a copyable HTML snippet:
Design the fields
The Forms tab appears only for static-site apps. In Design, add, remove and reorder fields. Each field has a type (text, email, long text, dropdown, checkbox, number, web address or hidden), a label, a name, a required flag, a placeholder and optional minimum and maximum length. A live preview sits beside the editor and the snippet under Embed follows your edits in three variants: HTML, HTML with afetch script, and minimal markup without labels.
A form that never sets fields keeps the default name, email and message fields. Once fields are set, the server validates each post (required, type, length, dropdown choice). Invalid posts answer 422 with {"error":..., "fields":{"email":"invalid_email"}}. Submitted names that are not declared are dropped, not stored; the spam trap field is always checked first.
Optional extras: a success message (shown on the thank-you page and returned as message to JSON clients) and allowed sites. When allowed sites are set, browser posts from other origins get 403 and CORS only echoes the allowed origin; requests with no Origin header (curl, servers) still pass.
The endpoint address
The snippet uses the app’s first https domain. With no domain it uses the configured dashboard (public) URL. With neither, the page shows a warning and a Set the public URL button, because that endpoint only works from the machine running Levelrail.Test it
Send a test submission stores a sample post through the real handler, marked Test in the inbox, without notifying channels. CLI:levelrail-cli forms test <app> <name>.
What the endpoint accepts
A browser post is redirected (303) to the form’s redirect URL, or shown a plain thank-you page when none is set. A request that sends JSON or
Accept: application/json gets {"ok":true} instead, so a fetch call from your own script does not navigate away.
Unless the form lists allowed sites, the endpoint answers cross-origin requests (Access-Control-Allow-Origin: *) because it is write-only: it takes no credentials and never returns stored data. Unknown form names answer 404, and one form’s address can never read another form’s submissions.
Spam protection
- Spam trap. The hidden
bot-fieldinput is left empty by people and often filled by bots. A post that fills it is stored as spam, shown to the sender exactly like a success, and never sends a notification. Change the field name in the form settings. - Link flood. A post with more links than
FORMS_SPAM_MAX_LINKS(default 5) across its fields is stored as spam. - Rate limits. Each client address may post
FORMS_RATE_PER_IP_PER_MINUTEtimes a minute (default 10), and each form acceptsFORMS_RATE_PER_FORM_PER_MINUTE(default 120). Over the limit gets 429 withRetry-After. The client address is read the same way as the rest of the API, soX-Forwarded-Foris trusted only from a configured proxy. - Review. Spam is kept, not dropped. Mark a submission as spam or not spam in the inbox, or with
forms spamandforms unspam.
Inbox, notifications and export
The inbox lists the newest submissions first, loads more as you scroll, and searches field text on the server. Filter by spam or not spam, flag or delete single submissions, and export the current filter as CSV. Cells that a spreadsheet would run as a formula are prefixed with a quote. Pick any notification channel in the form settings to be told about each new, non-spam submission. The message lists up to eight fields, flattened and shortened, and links to the inbox when a dashboard URL is set. A failing channel never fails the post: the submission is already saved, the failure is logged, and the visitor still sees success.Personal data, retention and deletion
Submissions usually contain personal data, so the defaults are conservative.- The sender’s address is never stored. Each submission keeps a salted hash (different salt per form, so hashes cannot be matched across forms), enough to spot repeat senders in the inbox but not to recover the address.
- Submissions older than
FORMS_RETENTION_DAYS(default 90) are deleted automatically. Set it to0to keep them until you delete them. - A form keeps at most
FORMS_MAX_STORED_PER_FORMsubmissions (default 5000). When the cap is reached, spam is dropped first, then the oldest. - Deleting a submission, a form, or the app removes the stored data. Nothing is kept elsewhere.
- Reading submissions needs the
read:sensitiveability; creating, editing and deleting forms needsdeploy. - Tell your visitors what you collect. The control plane stores what the form sends and nothing about the browser.
Limits and settings
Limits are read when the first post arrives after a start, so restart the control plane after changing them.
CLI
What it does not do yet
- It does not find forms in your HTML automatically. You create each form and use its endpoint.
- It does not verify CAPTCHA tokens or call third-party spam services.
- It does not store uploaded files.
- It does not send a confirmation back to the visitor.