Birta
← Back to blog

· Alex Vale

Add a working contact form to a static site in 2 minutes

A contact form on a static site needs somewhere to send submissions. Describe it to your agent and leads land in your Birta panel and inbox. No server or PHP.

Add a working contact form to a static site in 2 minutes

A static site can't receive a form submission on its own; it needs somewhere to send it. With Birta, you describe the form to your agent, the fields you want, and it declares the form and writes the markup. From then on, every submission lands in your project's panel, and a summary email follows within 15 minutes.

This walks through the real contract behind that: what your agent actually writes, where a submission goes, and what Birta doesn't do (Telegram, webhooks, a CAPTCHA), so you're not left guessing.

Why a static site can't just "have" a form

A plain HTML form is front-end markup, and nothing more. When someone clicks submit, the browser sends that data somewhere, and a static site has no back end sitting behind it to catch it. A static host only serves files. It doesn't process anything.

This is the wall almost everyone hits first. You build a landing page with an AI tool, it looks finished, and then a <form> tag sits there expecting the submit button to just work. It doesn't, because nothing is listening on the other end.

That's not a bug in your markup. It's the nature of static hosting: fast, cheap, and hard to break into precisely because no server-side code is running that could fail or be attacked. The tradeoff is that a form needs one more piece: somewhere that actually accepts the request and does something with it.

What "add a form" actually means here

On Birta, that piece is built in, but not as a drop-in widget. Say what you want to collect to the agent that's already building your site: a name, an email, a message, a checkbox for consent. The agent declares the form on your project, a set of fields, each with a type and whether it's required.

Then the agent writes the form's actual markup itself, styled to match your site. Birta gives no ready-made form block and doesn't dictate how the form looks.

That's a deliberate difference from widget-based tools such as Formbricks, which embeds its own form inside an iframe on your page. The box works, but it looks like the tool, not your site. Here the HTML is yours, and so is the "thanks for reaching out" line the visitor reads. The full contract lives in the lead-forms guide if you want the operational reference alongside this.

Where a submission actually goes

This is the part most guides skip, and it decides whether the form works at all. A submission posts to POST /_forms/<key> on the site's own host, whatever address it's live at, standard or a connected domain. The body is JSON, and the endpoint is public: no API key, no project id, no secret token belongs anywhere in the markup, because a visitor's own browser is the one sending it.

A successful submission gets 201 {"status": "accepted"}. A rejected one comes back with a specific code: 400 when a field doesn't match the declared form, 403 if you're testing on a draft (a version preview never accepts a submission, on purpose), 404 if the form key doesn't exist, 413 if the payload is too large, 429 past a rate limit. Your agent reads that code and turns it into whatever the visitor should see next. Birta doesn't write that message for you.

The body only carries the keys you declared, nothing else. An absent field, null, an empty string, and an empty list all count the same way: not filled in. A required field left that way is refused. An optional one is simply left out of the lead, no error, no empty row in your panel.

One field is stricter than it looks: consent. Most contact forms have a checkbox for permission to reply, and a consent field here takes a JSON boolean, where only true counts as given. Anything else, including a missing value or the string "true" instead of the boolean, is refused as consent_not_given. A vague checkbox that any truthy-looking value could satisfy isn't really consent. Requiring an actual boolean is what keeps it meaning what it says.

What you can actually collect

A form can hold up to 20 fields, and a project can declare up to 5 forms, splitting an order form from a callback request, say. Each field is one of a handful of types: short text, long text, email, a single choice from a list, a checkbox for consent. Short text tops out at 500 characters, long text at 5,000, email addresses at 254, and a whole submission at 65,536 bytes.

Those are this deployment's configured limits as of publishing, not a number carved into the product forever. What matters is the shape: room for a real inquiry, not enough for someone to paste in a novel.

Reading what comes in

Every accepted submission shows up in your project's panel, marked "new," with the date, the form it came from, and every field the visitor filled in. A site that collects more than one kind of request, an order form and a callback form, say, splits them out so you're not scanning one long list for the ones that matter.

You don't have to sit there refreshing it. The notification is an email, not a live push: a submission opens a 15-minute window, and when it closes, one message goes to everyone with access to the project. It names the project and lists every submission that landed in the window, each with its form, its time, and its fields. A single late submission still gets its own email, 15 minutes after it arrives.

When the form sits on a page built to collect inquiries, how many of those leads your plan lets you open starts to matter as much as the form itself. When there is nothing to reply to yet, because the thing you are building is not out, the form shrinks to one field and the job becomes collecting a waitlist. And on a site of several pages, the link that sends people to the form usually belongs in the footer, because that is the one block a visitor can reach from wherever they gave up.

And this is where it stops

Three things this isn't. No Telegram delivery, no webhook, and no choice of where a submission goes: it lands in the panel, and a digest follows by email, and that's the whole delivery story. There's also no accept-payment step built into the form.

If your project needs any of that today, it needs a different layer than this brick gives you.

Keeping spam out without a CAPTCHA

Birta doesn't put a CAPTCHA in front of your form. A plain rate limit does the work instead. A visitor can send up to 5 submissions in a 10-minute window, and a project accepts up to 200 submissions an hour in total. Past either limit, a submission comes back as 429 too_many_submissions, and your agent can turn that into a line on the page.

That's not smart spam filtering. It won't recognize a script pacing itself under the limit, and it won't score a message for spammy content. What it actually stops is the ordinary case: a double-submitted form, a broken retry loop, someone hammering refresh. For most contact forms, that covers most of what goes wrong.

Describe the form. Your agent builds it.

Tell your agent what fields you need, and it writes the form. Submissions land in your panel within minutes.

See how it connects

Common questions