Skip to content
LazyFrog

Forms

How a contact or sign-up form works on LazyFrog with no backend code: submissions saved in your database, spam filtering and email notifications.

A contact or sign-up form needs no backend: <form method="post" action="/_lf/form/contact"> (or a fetch POST with JSON, which gets {"ok": true, "id": …} back) stores each submission in the environment's database, table lazyfrog_forms.submissions (form, created_at, data jsonb, spam). Fields starting with _ are instructions: _next is the path to send the visitor to afterwards (otherwise back to the page with ?submitted=1); add a hidden, empty <input name="_gotcha"> (it catches bots that fill every field). A post that doesn't come from a browser on the site's own page, or that looks like spam, is kept as spam too, and is answered like any post. Post from the page (an HTML form or the browser's fetch), never from the app's server, or it lands in spam. Text fields only, 64 KB per submission; each visitor is limited to a modest number of submissions per hour across all forms, plenty for a real visit. Read them with the forms tool or from the app. Add a privacy notice when a form collects personal data. The owner reads them in the portal (Project › Messages, with a Spam tab to check and put back), or asks you.

Emailing the owner about new submissions. What happens to a submission is part of the plan (its intake, see "Before you build something new"): set it as agreed with forms notify: mode each (an email for each new submission), daily (one summary a day, at 08:00 UTC) or off (just Messages), recipients "owner" or the email addresses of the project's owner and editors, and form for one form only (without it, every form). Without mode it says the current setting. It mails only the project's own people with a confirmed address, never an address you type (so a form can't be used to mail anyone else); a viewer can't be chosen. Spam never sends an email; after 10 emails in an hour the rest of that hour come as one summary, and the email says so. The visitor's words arrive as plain text, marked as theirs, cut short and never as links; replying reaches the visitor when a field holds their email address. Each email has a one-click turn-off for its recipient, the owner can change it on Project › Messages, and the change is recorded under your name. These emails are LazyFrog's own: they work whether or not the owner has connected an email provider for the app. LazyFrog never emails the visitor: the confirmation on the page (the _next page, or the page with ?submitted=1) is what they see, so write it to say what happens next ("Thanks, we reply within a day"). A submission on the preview sends a test email, so you can check a doneWhen item such as "a test submission arrives where the plan says". Don't build an automatic email back to whoever filled in a public form either: anyone can type someone else's address into it, which makes the site a way to mail strangers. Spam still getting through: look at forms list with include_spam, sort with forms mark_spam / delete (the owner can in Messages too); for more, the app's own handler with Cloudflare Turnstile (its secret key through secrets request), knowing /_lf/form stays open; allow_countries in routing blocks the whole site, not only the form.

People's personal data (a member list, contacts, bookings): import only what the feature needs (drop notes, health details, dates of birth); ask before showing members each other's phone numbers and let people opt out; keep such pages noindex and no-store; delete the raw upload after importing (snapshots keep a copy for the undo history); never make a project that holds people's data remixable (a remix copies the preview database). Moving it to the live site: for a one-off, a confirmed production db_query (say that the data passes through you, the AI); for a list the owner keeps updating, build an owner-only import page on the live site behind routing auth, so it goes straight from their browser to the live database. Health details a form asks for (the reason for a visit): optional and a few words, shown only on the owner's page, never in emails, deleted on a schedule (a cron, with the live site always on), and said in the privacy notice (special category data under GDPR; special personal information under South Africa's POPIA). Pages you screenshot or query pass that data through you: say so.

Health records and other special category data (a clinic, a therapist, a pharmacy): the opposite of a form's optional reason for a visit. Records are required and the clinic must keep them (in South Africa the HPCSA's 6 years; for children until they turn 21; in the UK 8 years for adults): an erasure request usually means restricting the record until its retention ends, not deleting it, and the clinic decides. Build: roles enforced in the database (row-level security for who may read clinical notes, not only hidden buttons; reception sees appointments and contact details only), an append-only log of who opened which record, encryption in the app of the most sensitive fields (notes, intake answers, documents) with AES-256-GCM and a key the owner makes and pastes through secrets request (they keep a copy: values are never shown back, and without it an export can't be read), one export per patient that includes their notes, and an erasure log re-applied after any restore. LazyFrog runs in Germany; its sub-processors and the data processing agreement are at https://lazyfrog.dev/legal: point the owner to them, and say that a signed DPA and their own legal advice come before real patients' data.

Marketplaces where adults meet children (tutors, coaches, sitters): children have no accounts; the parent books, pays and messages. A person vets every adult before they are listed (police clearance, a teaching register, the UK's DBS), contact details stay hidden until a booking is paid, messages are kept, can be reported, and can't be deleted by their writers, and offenders can be suspended for good. A safeguarding page with real contacts (Childline 116 in South Africa, 0800 1111 in the UK), reviews that don't describe the child, and images taken down at once (new names, above). The platform's content check reads the home page at publish, never what users post: moderation is the app's.

Apps for children or schools (a class quiz, a club for under-18s): the school or club is the controller and decides (in the UK, usually public task rather than consent; a school will want a data protection impact assessment and a processing agreement before pupils' data goes in). Follow the ICO Children's Code: collect the least (a first name and initial, no email if it can be avoided), private by default, nothing shown to other children unless they opt in, and then only a made-up nickname; no analytics or tracking; a privacy notice in words a child understands; a deletion at the end of the year (or season) that the owner runs; a way to hand a parent their child's data. Put a preview password on before real children's data goes into the preview. Deleted data leaves the live site at once and the undo history after its retention (below).

Privacy on their site: the policy is the owner's to give (you can offer a page with placeholders; don't invent legal text). Tracking cookies need consent first (GDPR, Québec's Law 25); a functional cookie such as a language choice doesn't. What LazyFrog itself keeps about visitors, for their policy: request logs (kept for the plan's log retention), and for platform forms the submission with a hashed IP, the browser's user agent and the page it was sent from (kept until the owner deletes it). LazyFrog sets no cookies on their site.

For your AI: this page as Markdown · llms.txt