Contact form without PHP
Replace your contact.php with Formlane: point a plain HTML form at one endpoint. No PHP, no SMTP setup. Submissions stored in Frankfurt.
A classic contact.php needs PHP, a working mail() or SMTP login, and updates for the PHP version it runs on. Static hosts such as GitHub Pages, Netlify and Cloudflare Pages don't run PHP at all. With Formlane, your HTML form keeps its fields and its design; only the action changes. Formlane receives the POST, filters spam, stores each submission and emails it to your account email.
Steps
- Sign in to Formlane, create a form and copy its access key (
fl_…). - In your form, change
action="contact.php"to the Formlane endpoint and add the hiddenaccess_keyfield. - Replace the
header('Location: …')redirect from your script with a hiddenredirectfield. - Deploy, submit the live form once and check your inbox and dashboard. Then delete
contact.phpfrom your server.
Code
<form action="contact.php" method="POST"> <label for="cf-name">Name</label> <input id="cf-name" type="text" name="name" required> <label for="cf-email">Email</label> <input id="cf-email" type="email" name="email" required> <label for="cf-message">Message</label> <textarea id="cf-message" name="message" required></textarea> <button type="submit">Send</button> </form>
<form action="https://smallwerk.com/formlane/api/submit" method="POST" accept-charset="UTF-8"> <input type="hidden" name="access_key" value="fl_YOUR_ACCESS_KEY"> <input type="hidden" name="subject" value="New message from example.com"> <input type="hidden" name="redirect" value="https://example.com/thanks.html"> <!-- Honeypot: hidden from people, leave it unchecked --> <input type="checkbox" name="botcheck" style="display:none" tabindex="-1"> <label for="cf-name">Name</label> <input id="cf-name" type="text" name="name" autocomplete="name" required> <label for="cf-email">Email</label> <input id="cf-email" type="email" name="email" autocomplete="email" required> <label for="cf-message">Message</label> <textarea id="cf-message" name="message" rows="5" required></textarea> <button type="submit">Send</button> </form>
The key is public by design: it only allows sending submissions to this one form. To be safe, restrict the form to your domain in its settings.
What replaces which part of contact.php
| In contact.php | With Formlane |
|---|---|
$_POST['name'], $_POST['message'] … | Any text field is stored as it is (up to 50 fields, 10,000 characters per field, 64 KB per submission) |
| Required-field checks | required in the HTML; Formlane rejects completely empty submissions |
header('Location: thanks.html') | Hidden redirect field with an absolute http(s) URL. Without it, Formlane shows a hosted thank-you page |
mail() / PHPMailer with SMTP login | Email notification for each submission (to your account email), reply-to set to the sender. Plus a dashboard with CSV export |
| Hand-written spam checks or a captcha | Honeypot, rate limit and content rules, see below |
Optional: stay on the page
If you'd rather show a message without a page change, send the form with fetch(). With Accept: application/json, Formlane answers {"success": true, "message": "Form submitted successfully", "id": "…"} or {"success": false, "error": "limit_reached", "message": "…"} with a 4xx/5xx status. message is always English, so the snippet below shows its own text per error code.
<script>
const MESSAGES = {
limit_reached: "This form can't take more messages this month. Please email us directly.",
rate_limited: "Too many messages in a short time. Please try again in a few minutes.",
origin_not_allowed: "This form only accepts messages from its own website.",
};
const form = document.querySelector('form[action*="formlane"]');
if (form) form.addEventListener("submit", async (e) => {
e.preventDefault();
const res = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const json = await res.json().catch(() => ({}));
// Spam caught by the honeypot or the content rules also gets success: true.
form.outerHTML = json.success
? "<p>Thanks! We'll get back to you.</p>"
: "<p>" + (MESSAGES[json.error] || "Sorry, that didn't work. Please try again later.") + "</p>";
});
</script>Spam protection without a captcha
- Honeypot: the hidden
botcheckcheckbox (_gotchaand_honeyalso work). If a bot ticks it, Formlane returns a normal success response and stores nothing. - Rate limit: 10 submissions per 10 minutes per visitor and form.
- Content rules: messages with 4 or more links, link markup or typical spam phrases go to a separate spam tab. Spam doesn't count toward your monthly limit.
- Allowed domains: limit the form to your own domain in its settings.
Where the data is stored
Submissions are stored in Frankfurt, in a Supabase database in the eu-central-1 region. The app runs on Vercel with compute in Frankfurt; static assets and the edge network are global. Notification emails are sent through Resend, a US company. All providers are listed in the privacy policy. Formlane is run by Mindcaptain Services OÜ in Tallinn, Estonia. A data processing agreement is included in the Terms (Art. 28 GDPR, smallwerk.com/dpa), and IP hashes are deleted after 30 days.
Related guides
FAQ
Does this work on GitHub Pages, Netlify and Cloudflare Pages?
Yes. Those hosts don't run PHP and only serve your static files; the form posts directly to Formlane.
Can I keep my field names?
Yes. Formlane stores every text field you send, up to 50 fields and 10,000 characters per field. Only access_key is required. A field called email is used as the reply-to address.
What do I do with my old contact.php?
Delete it from your server once the new form works, together with any SMTP password it contains. Then nothing on your host still accepts form posts.
Will I get an email for each message?
Yes. Formlane sends an email notification for each submission to your account email, with reply-to set to the sender's address. Every submission is also in your Formlane dashboard, where you can export it as CSV. Suspected spam and test submissions from the dashboard are not emailed.
Can visitors upload files?
Not yet. Formlane accepts text fields only.
What happens at the monthly limit?
New submissions are rejected with a limit_reached error until the next calendar month or until you upgrade. Submissions you already have are kept.
Get your access key
Free for 250 submissions a month. Submissions stored in Frankfurt.
Get your access key