Forms & Data Capture

Build a contact form that saves submissions to Lovable Cloud and pings you by email.

18 min read·6 parts·beginner

Until now, everything you built has been one-way: you write, visitors read. A form flips the direction. Someone on the other side of the internet types a message and — if you wire it up right — it lands in your database and pings your inbox thirty seconds later. This lesson is where your site stops being a brochure and starts being an app.


Part 1 · Why forms change everything

A working form is the smallest possible version of a "backend". You need somewhere to store what the visitor typed, some way to tell you it arrived, and enough guardrails that the form doesn't get abused. Get those three right on a contact form and you've got the mental model for every future feature — signups, bookings, orders, comments.

Figure 1.1
StaticVisitor readsVisitor leavesNo trace, no follow-upForm-poweredVisitor typesYou get an emailRow in the databaseYou reply. Loop closes.
Left: static site — every visit is a dead end. Right: form-powered site — visitor input becomes data you can read, count, and reply to.
What "backend" means, without the jargon
A backend is a place your site can put things and get them back later, safely. Lovable Cloud gives you one with a database, auth, and file storage already wired in — you don't spin up servers.

Part 2 · Anatomy of a working form

Every real form has six pieces. Miss one and it either annoys users or quietly drops data. Name them in your prompt and the agent will build all six.

Figure 2.1
  1. 1Fields

    Name, email, message — labeled, placeholder text, correct input types.

  2. 2Validate

    Required, real email, length limits. Show errors inline.

  3. 3Submit

    Disable button, show loading, prevent double-send.

  4. 4Store

    Write a row to the database with a timestamp.

  5. 5Notify

    Email you (or Slack, or admin dashboard).

  6. 6Feedback

    Success message or clear recovery on error.

The six pieces of a working form. Fields and submit are visible; validation, storage, notification, and feedback happen in the seams.
Avoid
Client-only validation
Checks happen in the browser. Fine for UX, but anyone can bypass them with dev tools. Garbage lands in your database.
Do
Client + server validation
Browser gives instant feedback; the server re-checks before writing. Same rules, two places. This is non-negotiable.

Part 3 · Building the contact form

A good contact-form prompt names all six pieces. You don't have to know the code — you just have to be specific about the behavior.

"Add a contact form to /contact with three fields — name, email, and message. Required, real email, message between 10 and 1000 characters. Show inline errors. On submit, disable the button, save to Lovable Cloud, and show a success card that thanks them by name. If the save fails, keep their text and show a retry."
Figure 3.1
Success
ahmadnajmi.com/contact
Thanks, Sarah — got it.
I'll get back to you within 2 working days.
Error
ahmadnajmi.com/contact
Please enter a valid email address.
Two states of the same form. Design both — the success state is where trust is earned.

Small things that make a big difference

  • Use the right input types: type="email" triggers the email keyboard on phones.
  • Label every field. Placeholder text is not a label.
  • Disable the submit button while sending — otherwise people click twice and you get duplicates.
  • Keep the user's text on error. Never clear a form the visitor just spent a minute filling in.

Part 4 · Saving to Lovable Cloud

The agent handles the wiring, but you should know the shape. A contact submission becomes a row in a table — say contact_submissions — with a column for each field, plus an auto created_at timestamp.

Figure 4.1
idnameemailmessagecreated_at
a1f0…Sarah Leesarah@studio.coInterested in a workshop for our team…2026-07-19 09:42
c2d3…Ahmad R.ahmad@utm.myCollaboration on NexScholar research…2026-07-19 11:08
A contact form row: exactly what the visitor typed, plus a timestamp and an id. Nothing clever, nothing extra.

Row-level security — even here

A contact form feels harmless, but the same table might one day hold phone numbers or private notes. Turn on row-level security from day one and be explicit: anyone can insert, only I can read.

  • Allow anonymous visitors to INSERT into contact_submissions but never SELECT.
  • Only authenticated admins can read submissions in the admin dashboard.
  • Reject inserts where message is empty or over 2000 characters at the database level.
Why database-level rules matter
Client-side rules protect UX. Database-side rules protect data. Even if a bug removes your validation in the frontend tomorrow, the database will still refuse a 10,000-character message.

Part 5 · Getting notified

A submission sitting in a database no one checks is worse than no form at all — the visitor thinks you got it, and you didn't. You need at least one notification channel from day one.

Avoid
Silent database
Rows pile up. You remember to check once a week. Two leads go cold, one thinks you're ignoring them.
Do
Email + dashboard
Every submission emails you within seconds. The admin dashboard is the archive. You reply from your inbox and mark it done.

Prompts that wire notifications

  • When a new contact_submissions row is inserted, send me an email at ahmadnajmi.an@utm.my with the visitor's name, email, and message.
  • Add a Contact tab in the admin dashboard that lists submissions newest-first with a "reply" mailto link.
  • Mark a submission as read when I open its detail view so unread ones are easy to spot.

Part 6 · Spam, abuse & trust

The moment your form is public, bots find it. Not eventually — within hours. You don't need enterprise anti-fraud on day one, but you do need two boring defaults: a honeypot field and a rate limit.

Figure 6.1
Human seesnameemailmessageBot sees (HTML)nameemailwebsite (hidden — trap!)message
A honeypot is an invisible field. Real humans can't see it, so they leave it blank. Bots fill every field they find — and give themselves away.
Avoid
Open form
No trap, no rate limit. A bot submits 400 fake leads overnight and your inbox is unreadable in the morning.
Do
Honeypot + rate limit
Hidden trap field blocks most bots for free. A 5-per-minute per-IP limit stops the rest. Zero friction for real visitors.

Prompts that harden the form

  • Add a hidden honeypot field called "website" to the contact form. Reject the submission on the server if it's filled.
  • Rate limit contact submissions to 5 per IP per minute using Lovable Cloud.
  • Never trust the client — re-validate name, email, and message length on the server before inserting.
Rule of thumb
Anything the browser sends can be faked. Treat the frontend as a UX layer and the database as the source of truth. Same rules, enforced twice.

Forms cheat sheet

Six moves that turn a form from "looks like a form" into one you can actually rely on.

  • Name the six pieces — fields, validate, submit, store, notify, feedback. Every prompt hits all six.
  • Design the success state — a form without a good "thanks" screen doesn't feel finished.
  • Validate twice — browser for UX, database for truth. Same rules in both places.
  • RLS from day one — insert-only for visitors, read-only for you. Never public SELECT.
  • Notify immediately — email or Slack the moment a row lands. Dashboards are the archive, not the alert.
  • Honeypot + rate limit — the two boring defaults that keep bots out without hurting real visitors.
Recap
Your site can now take input, store it safely, tell you about it, and shrug off basic abuse — the whole loop of a real app in one contact form. In Lesson 7 we'll get comfortable with what happens when things go wrong: reading errors, using history to roll back, and escaping loops where the agent keeps making the same mistake.