Booking form creator
Change the service names to what you actually offer. A studio might list a cut, a color and a consult. A clinic page might list new patient and follow-up. The date field is type="date", so the browser shows its own date picker.
Fields, no AI
Live preview
Preview only. Nothing is sent from this page.
Request a time
Copy the code
Replace fk_your_access_key with your own Formgong access key. The placeholder does not accept submissions. The honeypot field is named botcheck. Leave it empty.
The HTML form posts in the browser. React and Vue show an error on the page if the request fails. These class names need Tailwind on your site. This page does not load Tailwind. Bootstrap 5 class names need Bootstrap on your site. This page does not load Bootstrap.
<style>
#fg-fk_your_access_key{box-sizing:border-box;max-width:520px;width:100%;padding:28px;border:1px solid #dbe2ea;border-radius:8px;background:#fff;color:#0f172a;font:16px/1.5 system-ui,sans-serif;display:grid;gap:16px}#fg-fk_your_access_key *{box-sizing:border-box}#fg-fk_your_access_key h2{font-size:24px;line-height:1.3;margin:0 0 4px;color:#0f172a}#fg-fk_your_access_key label{display:grid;gap:6px;font-weight:500}#fg-fk_your_access_key input,#fg-fk_your_access_key textarea,#fg-fk_your_access_key select{width:100%;font:inherit;min-height:44px;border:1px solid #94a3b8;border-radius:8px;padding:10px 12px;background:#fff;color:#0f172a}#fg-fk_your_access_key textarea{min-height:96px;resize:vertical}#fg-fk_your_access_key .fg-reminder-consent{display:flex;align-items:flex-start;gap:10px;font-weight:400}#fg-fk_your_access_key input[type=checkbox]{width:20px;min-height:20px;flex-shrink:0;margin:2px 0}#fg-fk_your_access_key button{font:inherit;font-weight:600;min-height:48px;padding:12px 20px;border:0;border-radius:8px;background:#4338ca;color:#fff;cursor:pointer}#fg-fk_your_access_key :focus-visible{outline:3px solid #4338ca;outline-offset:3px}#fg-fk_your_access_key button:disabled{cursor:wait;opacity:.7}#fg-fk_your_access_key p{margin:0}#fg-fk_your_access_key fieldset{border:0;padding:0;margin:0;display:grid;gap:8px;min-width:0}#fg-fk_your_access_key legend{font-weight:500;padding:0;margin-bottom:6px}#fg-fk_your_access_key .fg-choice{display:flex;align-items:center;gap:10px;font-weight:400}#fg-fk_your_access_key input[type=radio]{width:20px;min-height:20px;flex-shrink:0;margin:0}#fg-fk_your_access_key .fg-trap{position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
</style>
<form id="fg-fk_your_access_key" action="https://formgong.com/submit" method="POST">
<h2>Request a time</h2>
<input type="hidden" name="access_key" value="fk_your_access_key">
<input type="hidden" name="_lang" value="en">
<label>Name * <input type="text" name="name" required autocomplete="name"></label>
<label>Email * <input type="email" name="email" required autocomplete="email"></label>
<label>Phone * <input type="tel" name="phone" required autocomplete="tel"></label>
<label>Service * <select name="service" required><option value="">—</option><option value="Intro call">Intro call</option><option value="Studio visit">Studio visit</option><option value="Follow-up">Follow-up</option></select></label>
<label>Preferred date * <input type="date" name="preferred_date" required></label>
<label>Time of day * <select name="time_window" required><option value="">—</option><option value="Morning">Morning</option><option value="Afternoon">Afternoon</option><option value="Evening">Evening</option></select></label>
<label>Note <textarea name="note"></textarea></label>
<div class="fg-trap" aria-hidden="true"><label>Leave this field empty<input name="botcheck" tabindex="-1" autocomplete="off"></label></div>
<button type="submit">Request this time</button>
</form><form class="relative mx-auto grid w-full max-w-lg gap-4 rounded-lg border border-slate-200 bg-white p-6 text-base text-slate-900" action="https://formgong.com/submit" method="POST"> <h2 class="text-2xl font-semibold">Request a time</h2> <input type="hidden" name="access_key" value="fk_your_access_key"> <input type="hidden" name="_lang" value="en"> <div class="grid gap-1"><label class="font-medium" for="tw-name">Name *</label><input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-name" type="text" name="name" autocomplete="name" required></div> <div class="grid gap-1"><label class="font-medium" for="tw-email">Email *</label><input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-email" type="email" name="email" autocomplete="email" required></div> <div class="grid gap-1"><label class="font-medium" for="tw-phone">Phone *</label><input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-phone" type="tel" name="phone" autocomplete="tel" required></div> <div class="grid gap-1"><label class="font-medium" for="tw-service">Service *</label><select class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-service" name="service" required><option value="">Choose</option><option value="Intro call">Intro call</option><option value="Studio visit">Studio visit</option><option value="Follow-up">Follow-up</option></select></div> <div class="grid gap-1"><label class="font-medium" for="tw-preferred_date">Preferred date *</label><input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-preferred_date" type="date" name="preferred_date" required></div> <div class="grid gap-1"><label class="font-medium" for="tw-time_window">Time of day *</label><select class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-time_window" name="time_window" required><option value="">Choose</option><option value="Morning">Morning</option><option value="Afternoon">Afternoon</option><option value="Evening">Evening</option></select></div> <div class="grid gap-1"><label class="font-medium" for="tw-note">Note</label><textarea class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700 min-h-24" id="tw-note" name="note"></textarea></div> <div class="absolute start-0 top-0 h-px w-px overflow-hidden" aria-hidden="true"><label>Leave this empty<input name="botcheck" tabindex="-1" autocomplete="off"></label></div> <button class="min-h-12 rounded-md bg-indigo-700 px-4 py-3 font-semibold text-white" type="submit">Request this time</button> </form>
<form class="position-relative mx-auto" style="max-width:32rem" action="https://formgong.com/submit" method="POST"> <h2 class="h4 mb-3">Request a time</h2> <input type="hidden" name="access_key" value="fk_your_access_key"> <input type="hidden" name="_lang" value="en"> <div class="mb-3"><label class="form-label" for="bs-name">Name *</label><input class="form-control" id="bs-name" type="text" name="name" autocomplete="name" required></div> <div class="mb-3"><label class="form-label" for="bs-email">Email *</label><input class="form-control" id="bs-email" type="email" name="email" autocomplete="email" required></div> <div class="mb-3"><label class="form-label" for="bs-phone">Phone *</label><input class="form-control" id="bs-phone" type="tel" name="phone" autocomplete="tel" required></div> <div class="mb-3"><label class="form-label" for="bs-service">Service *</label><select class="form-select" id="bs-service" name="service" required><option value="">Choose</option><option value="Intro call">Intro call</option><option value="Studio visit">Studio visit</option><option value="Follow-up">Follow-up</option></select></div> <div class="mb-3"><label class="form-label" for="bs-preferred_date">Preferred date *</label><input class="form-control" id="bs-preferred_date" type="date" name="preferred_date" required></div> <div class="mb-3"><label class="form-label" for="bs-time_window">Time of day *</label><select class="form-select" id="bs-time_window" name="time_window" required><option value="">Choose</option><option value="Morning">Morning</option><option value="Afternoon">Afternoon</option><option value="Evening">Evening</option></select></div> <div class="mb-3"><label class="form-label" for="bs-note">Note</label><textarea class="form-control" id="bs-note" name="note" rows="4"></textarea></div> <div class="position-absolute top-0 start-0 overflow-hidden" style="width:1px;height:1px" aria-hidden="true"><label>Leave this empty<input name="botcheck" tabindex="-1" autocomplete="off"></label></div> <button class="btn btn-primary" type="submit">Request this time</button> </form>
import { useEffect, useRef, useState } from "react";
// Visitor language for Formgong messages, thank-you text and autoreply.
const LANG = "en";
export default function ContactForm() {
const captcha = useRef(null);
const feedback = useRef(null);
const [failed, setFailed] = useState(false);
const [busy, setBusy] = useState(false);
const [status, setStatus] = useState("");
useEffect(() => mountCaptcha(captcha.current, null), []);
useEffect(() => { if (failed && status) feedback.current?.focus(); }, [failed, status]);
async function onSubmit(event) {
event.preventDefault();
if (busy) return;
const form = event.currentTarget;
const body = new FormData(form);
body.set("access_key", "fk_your_access_key");
body.set("_lang", LANG);
setBusy(true);
setFailed(false);
setStatus("Sending…");
try {
const response = await fetch("https://formgong.com/submit", {
method: "POST", headers: { Accept: "application/json" }, body,
});
const result = await response.json();
setFailed(!result.success);
setStatus(result.success ? (result.message || "Sent. Thank you!") : (result.message || "Could not send. Please try again."));
} catch { setFailed(true); setStatus("Connection failed. Please try again."); }
finally { setBusy(false); resetCaptcha(form); }
}
return <form id="fg-fk_your_access_key" onSubmit={onSubmit} aria-busy={busy}>
<style>{"#fg-fk_your_access_key{box-sizing:border-box;max-width:520px;width:100%;padding:28px;border:1px solid #dbe2ea;border-radius:8px;background:#fff;color:#0f172a;font:16px/1.5 system-ui,sans-serif;display:grid;gap:16px}#fg-fk_your_access_key *{box-sizing:border-box}#fg-fk_your_access_key h2{font-size:24px;line-height:1.3;margin:0 0 4px;color:#0f172a}#fg-fk_your_access_key label{display:grid;gap:6px;font-weight:500}#fg-fk_your_access_key input,#fg-fk_your_access_key textarea,#fg-fk_your_access_key select{width:100%;font:inherit;min-height:44px;border:1px solid #94a3b8;border-radius:8px;padding:10px 12px;background:#fff;color:#0f172a}#fg-fk_your_access_key textarea{min-height:96px;resize:vertical}#fg-fk_your_access_key .fg-reminder-consent{display:flex;align-items:flex-start;gap:10px;font-weight:400}#fg-fk_your_access_key input[type=checkbox]{width:20px;min-height:20px;flex-shrink:0;margin:2px 0}#fg-fk_your_access_key button{font:inherit;font-weight:600;min-height:48px;padding:12px 20px;border:0;border-radius:8px;background:#4338ca;color:#fff;cursor:pointer}#fg-fk_your_access_key :focus-visible{outline:3px solid #4338ca;outline-offset:3px}#fg-fk_your_access_key button:disabled{cursor:wait;opacity:.7}#fg-fk_your_access_key p{margin:0}#fg-fk_your_access_key fieldset{border:0;padding:0;margin:0;display:grid;gap:8px;min-width:0}#fg-fk_your_access_key legend{font-weight:500;padding:0;margin-bottom:6px}#fg-fk_your_access_key .fg-choice{display:flex;align-items:center;gap:10px;font-weight:400}#fg-fk_your_access_key input[type=radio]{width:20px;min-height:20px;flex-shrink:0;margin:0}#fg-fk_your_access_key .fg-trap{position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}"}</style>
<h2>{"Request a time"}</h2>
<label>{"Name"} * <input type="text" name="name" required autoComplete="name" /></label>
<label>{"Email"} * <input type="email" name="email" required autoComplete="email" /></label>
<label>{"Phone"} * <input type="tel" name="phone" required autoComplete="tel" /></label>
<label>{"Service"} * <select name="service" required><option value="">—</option><option value={"Intro call"}>{"Intro call"}</option><option value={"Studio visit"}>{"Studio visit"}</option><option value={"Follow-up"}>{"Follow-up"}</option></select></label>
<label>{"Preferred date"} * <input type="date" name="preferred_date" required /></label>
<label>{"Time of day"} * <select name="time_window" required><option value="">—</option><option value={"Morning"}>{"Morning"}</option><option value={"Afternoon"}>{"Afternoon"}</option><option value={"Evening"}>{"Evening"}</option></select></label>
<label>{"Note"} <textarea name="note" /></label>
<div aria-hidden="true" style={{position:"absolute",insetInlineStart:0,top:0,width:1,height:1,overflow:"hidden",clipPath:"inset(50%)"}}><input name="botcheck" tabIndex={-1} autoComplete="off" /></div>
<button type="submit" disabled={busy}>{busy ? "Sending…" : "Request this time"}</button>
<p ref={feedback} role="status" tabIndex={-1}>{status}</p>
</form>;
}
function mountCaptcha(node, siteKey) {
if (!node || !siteKey) return () => {};
let disposed = false;
let widget;
function render() {
if (disposed || !window.turnstile) return;
window.turnstile.ready(() => {
if (disposed) return;
widget = window.turnstile.render(node, { sitekey: siteKey, language: node.dataset.language || document.documentElement.lang || "auto" });
node.dataset.widgetId = widget;
});
}
if (window.turnstile) render();
else {
let script = document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]');
if (!script) {
script = document.createElement("script");
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
script.async = true;
document.head.appendChild(script);
}
script.addEventListener("load", render, { once: true });
}
return () => { disposed = true; if (widget !== undefined) window.turnstile?.remove(widget); };
}
function resetCaptcha(form) {
const widget = form.querySelector("[data-turnstile]")?.dataset.widgetId;
if (widget !== undefined) window.turnstile?.reset(widget);
}<script setup>
import { ref } from "vue";
const status = ref("");
const busy = ref(false);
async function onSubmit(event) {
const form = event.currentTarget;
if (busy.value || !form.reportValidity()) return;
const body = new FormData(form);
body.set("access_key", "fk_your_access_key");
body.set("_lang", "en");
busy.value = true;
status.value = "Sending…";
try {
const response = await fetch("https://formgong.com/submit", {
method: "POST",
headers: { Accept: "application/json" },
body,
});
const result = await response.json();
status.value = result.success ? (result.message || "Sent. Thank you!") : (result.message || "Could not send. Please try again.");
} catch {
status.value = "Connection failed. Please try again.";
} finally {
busy.value = false;
}
}
</script>
<template>
<form style="position:relative;display:grid;gap:16px;max-width:32rem" @submit.prevent="onSubmit" :aria-busy="busy">
<h2>Request a time</h2>
<label>Name * <input type="text" name="name" required autocomplete="name"></label>
<label>Email * <input type="email" name="email" required autocomplete="email"></label>
<label>Phone * <input type="tel" name="phone" required autocomplete="tel"></label>
<label>Service * <select name="service" required><option value="">—</option><option value="Intro call">Intro call</option><option value="Studio visit">Studio visit</option><option value="Follow-up">Follow-up</option></select></label>
<label>Preferred date * <input type="date" name="preferred_date" required></label>
<label>Time of day * <select name="time_window" required><option value="">—</option><option value="Morning">Morning</option><option value="Afternoon">Afternoon</option><option value="Evening">Evening</option></select></label>
<label>Note <textarea name="note"></textarea></label>
<div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)"><input name="botcheck" tabindex="-1" autocomplete="off"></div>
<button type="submit" :disabled="busy">Request this time</button>
<p role="status" tabindex="-1">{{ status }}</p>
</form>
</template>Create a free form, then open Connect on that form in this browser. If it still has the default name, email and message fields, they are replaced with this form.
| This page | A live scheduler | |
|---|---|---|
| What the visitor gets | A request you confirm later | A reserved slot |
| Code | HTML you paste on your site | Usually their embed |
| Payment | None | Often a deposit |
| Telegram | Instant on the free plan | Depends on the product |
How to create an online booking form
- Decide what you need before the visit: name, a way to reply, the service, the day, and a rough time. The sample has those. Add a room or a practitioner with “Build it yourself, without AI” if you need one.
- Copy the HTML tab. The React and Vue tabs are there if the site is a component, not a static page.
- Replace
fk_your_access_key. Create the key in Formgong, then connect Telegram if you want the request in a chat immediately. - Paste the form on the page where people book. Publish and send yourself a test. Check the inbox and Telegram.
- When a request arrives, you still confirm the time. The form did not write it into a calendar.
A clinic can also start from the doctor appointment form template and use this generator when the fields should be different.
What this booking form does not do
People searching for a booking form often want a scheduler. This page is the request form, not the scheduler.
- No live availability. Two people can ask for the same morning. You sort that out when you reply.
- No payment. The form does not charge a card and does not hold a deposit.
- No reminder to the visitor on the free plan. Auto-reply is not on the free plan. You can still reply yourself from the inbox.
- No file upload in the generated code. A photo of a reference cut, for example, is not in this snippet.
Pick Calendly, Trafft or a similar scheduler when the visitor must see open slots and leave with a confirmed time. Pick this form when a request is enough and you want the message in Telegram and in an EU inbox.
Use cases that fit a request form
- A consultant’s intro call. The service list is the kind of call. The note is what they want to cover. You send a calendar invite after you read it.
- A studio or salon. Ask for the service and the day. Confirm by Telegram when you are at the chair, not from a public slot grid.
- A follow-up visit. One of the sample services is “Follow-up”. Change that label to the visit types you offer.
The time field is a list, not type="time". A list is easier to read than a free clock time, and it stops people asking for 03:10. If you need exact times, change that field to a select of real slots such as 09:00 and 11:00. Those slots are still requests, not reservations.
Free limits, email and storage
The booking form code is free to copy. A request is delivered only after you replace the placeholder key.
Copying the code needs no account. AI generations are limited to 5 an hour and 10 a day for each network, plus 100 a day for the whole site. When a limit is hit, use “Build it yourself, without AI”. That path does not call the model.
On the free plan, Telegram is instant and email is sent for every submission. The free plan is 300 submissions a month. Auto-reply is not on the free plan. Submission data is stored in the EU. Support is Formgong at support@formgong.com.
Your description is sent to the model so it can choose fields. It is not stored. Do not paste passwords, card numbers or other secrets.
Questions
What is a booking form generator?
It builds the fields for a booking request and gives you the HTML. This page starts with service, date, time of day and contact fields. It does not connect to a calendar.
How do I create an online booking form?
Copy the HTML, replace fk_your_access_key, and paste it on your page. Connect Telegram in Formgong if you want each request in a chat. You still confirm the time yourself.
Can visitors pick only open times?
Not with this form. It collects a preferred date and a time of day. It does not know your calendar. Use a scheduling product if the slot must be reserved on submit.
Does the date field show a calendar?
The field is type=date. Phones and desktop browsers show their own date picker. There is no extra calendar widget to load.
Is the booking form free?
Copying it is free and needs no account. The free Formgong plan includes 300 submissions a month. Telegram is instant, and email is sent for every submission.
Sources
Checked on 11.10.2026 against these public pages: MDN: input type=date, FormBackend booking form generator, makeform.ai appointment booking form, bizkitsmart booking form generator.