Forms
Point an HTML form at a Camplax URL and submissions land in the console — stored, spam-checked and optionally emailed to you. No backend to write.
A form endpoint is a public address that accepts form posts and keeps what they send. You create one per form — a contact form, a waitlist, an order enquiry — and set it as the form's action. There is nothing to deploy and no key to paste.
The two-minute version
In the console's Forms page, create an endpoint named after the form. It answers with a URL:
https://camplax.dev/v1/forms/frm_…
Point your form at it:
<form action="https://camplax.dev/v1/forms/frm_…" method="post">
<input name="name" required>
<input name="email" type="email" required>
<textarea name="message"></textarea>
<button>Send</button>
</form>
That is the whole integration. Submissions show up in the endpoint's inbox under Forms — newest first, unread ones marked — and the visitor is redirected to your redirectUrl when you've set one (a JSON caller gets {"ok": true} instead of the redirect).
The URL is public by design — it sits in your page's markup, like a Netlify form. It is unguessable, not secret: anyone who can read your HTML can submit to it, which is exactly what a form is for. Spam protection lives on the endpoint (rate limits and optional captcha), not on the URL.
Posting with fetch
JavaScript submits get a JSON answer:
const res = await fetch("https://camplax.dev/v1/forms/frm_…", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({ fields: { email, message } }),
});
const { ok } = await res.json();
A POST whose Accept header asks for JSON always answers {"ok": true} (or an error); a plain browser post follows the redirect. application/x-www-form-urlencoded and application/json both work — send files elsewhere, forms keeps text fields only.
Limits and spam rails
Submissions are public intake, so they are fenced:
- 20 posts per hour per visitor (by IP) and 60 per hour per endpoint — past that the answer is
429. - Up to 50 fields, each value capped at 16 KB, the whole submission at 64 KB — past that,
413. cf-turnstile-response— when the endpoint has captcha on, posts must carry a Turnstile token solved on your own site (see below) or they are refused with403.
A submission that passes the rails is written first; anything after that — the notify email — can fail without ever losing the post.
Captcha
Turn captcha on per endpoint in the console. Your site then renders a Turnstile widget inside the form with the project's site key — the console shows it next to the endpoint once captcha is on, and it is the same key the sign-in bot check issues:
<form action="https://camplax.dev/v1/forms/frm_…" method="post">
…
<div class="cf-turnstile" data-sitekey="0x4AAAA…"></div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async></script>
</form>
The token is verified against the hostname it was solved on, so a token from someone else's site never passes in yours. Captcha needs the form to live on a host the project serves — its *.camplax.app address or one of its custom domains.
Notify email
Set notifyEmail and every submission is mailed to that address: the fields, a link to the inbox. The address is whatever the project's owner typed — Camplax never stores it anywhere but the endpoint row.
Reading submissions from your app
The inbox is console data, but an app can read it with the CAMPLAX_PROJECT_TOKEN it already has (scope forms:read — the injected token picks it up on its next deploy):
GET /v1/projects/<slug>/forms/<endpoint id>/submissions
Authorization: Bearer $CAMPLAX_PROJECT_TOKEN
Answers newest-first, up to 1,000:
{ "submissions": [ { "id": "sub_…", "fields": { "email": "…", "message": "…" }, "ip": "…", "ua": "…", "createdAt": "…", "readAt": null } ] }
Submissions carry the sender's IP and user agent as received — use them for spam judgement, not display.
Managing endpoints
In the console: Forms lists every endpoint with its counts; a row opens its inbox and settings. The same surface over the API, with your console session:
| Method | Path | What it does |
|---|---|---|
GET | /v1/projects/<slug>/forms | List endpoints with submission counts |
POST | /v1/projects/<slug>/forms | Create — { name, notifyEmail?, redirectUrl?, captcha? } → { key, url } |
PATCH | /v1/projects/<slug>/forms/:id | Edit name, notify address, redirect, captcha, or switch off with { "enabled": false } |
DELETE | /v1/projects/<slug>/forms/:id | Delete the endpoint and its inbox |
GET | /v1/projects/<slug>/forms/:id/submissions | The inbox, newest first (also forms:read app keys) |
POST | /v1/projects/<slug>/forms/:id/submissions/:sid/read | Mark read; { "read": false } marks unread |
A project can have up to 25 endpoints. An endpoint switched off answers 404 — to the outside world it does not exist.