Support ticket form template
Category, urgency, where it happened and how to reproduce it — the details a first reply usually has to ask for.
Reviewed by Ritu Fadadu · Last reviewed
Preview and code
Preview
Code — replace YOUR_ENDPOINT_ID with your form's ID
<style>
.fb-form { display: grid; gap: 1rem; max-width: 36rem; }
.fb-field { display: flex; flex-direction: column; gap: 0.375rem; }
.fb-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.fb-label { font-weight: 600; }
.fb-optional { font-weight: 400; color: #6b7280; }
.fb-help { margin: 0; font-size: 0.875rem; color: #6b7280; }
.fb-input { box-sizing: border-box; width: 100%; padding: 0.625rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.5rem; background: #fff; color: #111827; font: inherit; }
.fb-input:focus { border-color: #2563eb; outline: 2px solid #2563eb; outline-offset: 1px; }
.fb-options { display: flex; flex-direction: column; gap: 0.5rem; }
.fb-option { display: flex; align-items: center; gap: 0.5rem; }
.fb-check { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: #2563eb; }
.fb-button { justify-self: start; min-height: 44px; padding: 0.625rem 1.25rem; border: 0; border-radius: 0.5rem; background: #2563eb; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
.fb-button:hover { background: #1d4ed8; }
.fb-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.fb-button:disabled { opacity: 0.6; cursor: wait; }
.fb-status { margin: 0; min-height: 1.5em; }
@media (min-width: 640px) {
.fb-form { grid-template-columns: 1fr 1fr; }
.fb-field, .fb-button, .fb-status { grid-column: 1 / -1; }
.fb-field--half { grid-column: auto; }
}
</style>
<form id="fb-form" class="fb-form" action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
<div class="fb-field fb-field--half">
<label for="fb-name" class="fb-label">Name</label>
<input type="text" id="fb-name" name="name" class="fb-input" required autocomplete="name">
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email</label>
<input type="email" id="fb-email" name="email" class="fb-input" required autocomplete="email">
</div>
<div class="fb-field fb-field--half">
<label for="fb-category" class="fb-label">What is it about?</label>
<select id="fb-category" name="category" class="fb-input" required>
<option value="">Choose…</option>
<option value="something-is-broken">Something is broken</option>
<option value="billing">Billing</option>
<option value="account-access">Account access</option>
<option value="feature-request">Feature request</option>
<option value="other">Other</option>
</select>
</div>
<fieldset class="fb-field fb-field--half fb-fieldset">
<legend class="fb-label">How urgent is it?</legend>
<div class="fb-options">
<label class="fb-option">
<input type="radio" name="urgency" value="low" class="fb-check" required>
<span>Low</span>
</label>
<label class="fb-option">
<input type="radio" name="urgency" value="normal" class="fb-check" required>
<span>Normal</span>
</label>
<label class="fb-option">
<input type="radio" name="urgency" value="blocking-my-work" class="fb-check" required>
<span>Blocking my work</span>
</label>
</div>
</fieldset>
<div class="fb-field">
<label for="fb-url" class="fb-label">
Page where it happened
<span class="fb-optional">(optional)</span>
</label>
<input type="url" id="fb-url" name="url" class="fb-input" placeholder="https://">
</div>
<div class="fb-field">
<label for="fb-steps" class="fb-label">What happened? Steps to reproduce</label>
<textarea id="fb-steps" name="steps" class="fb-input" required rows="5"></textarea>
</div>
<div class="fb-field">
<label for="fb-expected" class="fb-label">
What did you expect to happen?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-expected" name="expected" class="fb-input" rows="3"></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New support ticket">
<button type="submit" class="fb-button">Send ticket</button>
</form>
<style>
.fb-form { display: grid; gap: 1rem; max-width: 36rem; }
.fb-field { display: flex; flex-direction: column; gap: 0.375rem; }
.fb-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
.fb-label { font-weight: 600; }
.fb-optional { font-weight: 400; color: #6b7280; }
.fb-help { margin: 0; font-size: 0.875rem; color: #6b7280; }
.fb-input { box-sizing: border-box; width: 100%; padding: 0.625rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.5rem; background: #fff; color: #111827; font: inherit; }
.fb-input:focus { border-color: #2563eb; outline: 2px solid #2563eb; outline-offset: 1px; }
.fb-options { display: flex; flex-direction: column; gap: 0.5rem; }
.fb-option { display: flex; align-items: center; gap: 0.5rem; }
.fb-check { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: #2563eb; }
.fb-button { justify-self: start; min-height: 44px; padding: 0.625rem 1.25rem; border: 0; border-radius: 0.5rem; background: #2563eb; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
.fb-button:hover { background: #1d4ed8; }
.fb-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.fb-button:disabled { opacity: 0.6; cursor: wait; }
.fb-status { margin: 0; min-height: 1.5em; }
@media (min-width: 640px) {
.fb-form { grid-template-columns: 1fr 1fr; }
.fb-field, .fb-button, .fb-status { grid-column: 1 / -1; }
.fb-field--half { grid-column: auto; }
}
</style>
<form id="fb-form" class="fb-form" action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
<div class="fb-field fb-field--half">
<label for="fb-name" class="fb-label">Name</label>
<input type="text" id="fb-name" name="name" class="fb-input" required autocomplete="name">
</div>
<div class="fb-field">
<label for="fb-email" class="fb-label">Email</label>
<input type="email" id="fb-email" name="email" class="fb-input" required autocomplete="email">
</div>
<div class="fb-field fb-field--half">
<label for="fb-category" class="fb-label">What is it about?</label>
<select id="fb-category" name="category" class="fb-input" required>
<option value="">Choose…</option>
<option value="something-is-broken">Something is broken</option>
<option value="billing">Billing</option>
<option value="account-access">Account access</option>
<option value="feature-request">Feature request</option>
<option value="other">Other</option>
</select>
</div>
<fieldset class="fb-field fb-field--half fb-fieldset">
<legend class="fb-label">How urgent is it?</legend>
<div class="fb-options">
<label class="fb-option">
<input type="radio" name="urgency" value="low" class="fb-check" required>
<span>Low</span>
</label>
<label class="fb-option">
<input type="radio" name="urgency" value="normal" class="fb-check" required>
<span>Normal</span>
</label>
<label class="fb-option">
<input type="radio" name="urgency" value="blocking-my-work" class="fb-check" required>
<span>Blocking my work</span>
</label>
</div>
</fieldset>
<div class="fb-field">
<label for="fb-url" class="fb-label">
Page where it happened
<span class="fb-optional">(optional)</span>
</label>
<input type="url" id="fb-url" name="url" class="fb-input" placeholder="https://">
</div>
<div class="fb-field">
<label for="fb-steps" class="fb-label">What happened? Steps to reproduce</label>
<textarea id="fb-steps" name="steps" class="fb-input" required rows="5"></textarea>
</div>
<div class="fb-field">
<label for="fb-expected" class="fb-label">
What did you expect to happen?
<span class="fb-optional">(optional)</span>
</label>
<textarea id="fb-expected" name="expected" class="fb-input" rows="3"></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New support ticket">
<button type="submit" class="fb-button">Send ticket</button>
<p class="fb-status" role="status" aria-live="polite" data-fb-status></p>
</form>
<script>
document.getElementById("fb-form").addEventListener("submit", async (event) => {
event.preventDefault();
const form = event.currentTarget;
const status = form.querySelector("[data-fb-status]");
const button = form.querySelector('button[type="submit"]');
const fields = new FormData(form);
// Formboost accepts JSON or url-encoded bodies, not multipart, so send JSON.
const data = Object.fromEntries(fields);
// Makes the notification email replyable; the address still shows in the field table.
if (data.email) data._replyto = data.email;
button.disabled = true;
status.textContent = "Sending…";
try {
const response = await fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
status.textContent = "Thanks — your ticket has been received.";
} else if (response.status === 429) {
status.textContent = "Too many attempts. Please wait a minute and try again.";
} else {
status.textContent = "Something went wrong. Please try again.";
}
} catch {
status.textContent = "Could not send. Check your connection and try again.";
} finally {
button.disabled = false;
}
});
</script>
import { useState } from "react";
// Your form's endpoint, from the Formboost dashboard.
const ENDPOINT = "https://formboost.app/f/YOUR_ENDPOINT_ID";
export default function SupportTicketForm() {
const [sending, setSending] = useState(false);
const [message, setMessage] = useState("");
async function handleSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
const fields = new FormData(form);
// Formboost accepts JSON or url-encoded bodies, not multipart, so send JSON.
const data = Object.fromEntries(fields);
// Makes the notification email replyable; the address still shows in the field table.
if (data.email) data._replyto = data.email;
setSending(true);
setMessage("Sending…");
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
setMessage("Thanks — your ticket has been received.");
} else if (response.status === 429) {
setMessage("Too many attempts. Please wait a minute and try again.");
} else {
setMessage("Something went wrong. Please try again.");
}
} catch {
setMessage("Could not send. Check your connection and try again.");
} finally {
setSending(false);
}
}
return (
<form
className="grid max-w-xl gap-4 sm:grid-cols-2"
action={ENDPOINT}
method="POST"
onSubmit={handleSubmit}
>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-name" className="text-sm font-semibold text-gray-900">Name</label>
<input
type="text"
id="fb-name"
name="name"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autoComplete="name"
/>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-email" className="text-sm font-semibold text-gray-900">Email</label>
<input
type="email"
id="fb-email"
name="email"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autoComplete="email"
/>
</div>
<div className="flex flex-col gap-1.5">
<label htmlFor="fb-category" className="text-sm font-semibold text-gray-900">
What is it about?
</label>
<select
id="fb-category"
name="category"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
<option value="">Choose…</option>
<option value="something-is-broken">Something is broken</option>
<option value="billing">Billing</option>
<option value="account-access">Account access</option>
<option value="feature-request">Feature request</option>
<option value="other">Other</option>
</select>
</div>
<fieldset className="flex flex-col gap-1.5 m-0 min-w-0 border-0 p-0">
<legend className="text-sm font-semibold text-gray-900">How urgent is it?</legend>
<div className="flex flex-col gap-2">
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="urgency"
value="low"
className="h-4 w-4 accent-blue-600"
required
/>
<span>Low</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="urgency"
value="normal"
className="h-4 w-4 accent-blue-600"
required
/>
<span>Normal</span>
</label>
<label className="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="urgency"
value="blocking-my-work"
className="h-4 w-4 accent-blue-600"
required
/>
<span>Blocking my work</span>
</label>
</div>
</fieldset>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-url" className="text-sm font-semibold text-gray-900">
Page where it happened
<span className="font-normal text-gray-500">(optional)</span>
</label>
<input
type="url"
id="fb-url"
name="url"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
placeholder="https://"
/>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-steps" className="text-sm font-semibold text-gray-900">
What happened? Steps to reproduce
</label>
<textarea
id="fb-steps"
name="steps"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
rows={5}
></textarea>
</div>
<div className="flex flex-col gap-1.5 sm:col-span-2">
<label htmlFor="fb-expected" className="text-sm font-semibold text-gray-900">
What did you expect to happen?
<span className="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-expected"
name="expected"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
rows={3}
></textarea>
</div>
{/* Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. */}
<div
aria-hidden="true"
style={{ position: "absolute", left: "-9999px", width: "1px", height: "1px", overflow: "hidden" }}
>
<label htmlFor="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabIndex={-1} autoComplete="off" />
</div>
<input type="hidden" name="_subject" defaultValue="New support ticket" />
<button
type="submit"
className="min-h-11 justify-self-start rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-60 sm:col-span-2"
disabled={sending}
>
Send ticket
</button>
<p className="min-h-6 text-sm text-gray-700 sm:col-span-2" role="status" aria-live="polite">
{message}
</p>
</form>
);
}
<form
id="fb-form"
class="grid max-w-xl gap-4 sm:grid-cols-2"
action="https://formboost.app/f/YOUR_ENDPOINT_ID"
method="POST"
>
<div class="flex flex-col gap-1.5">
<label for="fb-name" class="text-sm font-semibold text-gray-900">Name</label>
<input
type="text"
id="fb-name"
name="name"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autocomplete="name"
>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-email" class="text-sm font-semibold text-gray-900">Email</label>
<input
type="email"
id="fb-email"
name="email"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
autocomplete="email"
>
</div>
<div class="flex flex-col gap-1.5">
<label for="fb-category" class="text-sm font-semibold text-gray-900">What is it about?</label>
<select
id="fb-category"
name="category"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
>
<option value="">Choose…</option>
<option value="something-is-broken">Something is broken</option>
<option value="billing">Billing</option>
<option value="account-access">Account access</option>
<option value="feature-request">Feature request</option>
<option value="other">Other</option>
</select>
</div>
<fieldset class="flex flex-col gap-1.5 m-0 min-w-0 border-0 p-0">
<legend class="text-sm font-semibold text-gray-900">How urgent is it?</legend>
<div class="flex flex-col gap-2">
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="urgency" value="low" class="h-4 w-4 accent-blue-600" required>
<span>Low</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input type="radio" name="urgency" value="normal" class="h-4 w-4 accent-blue-600" required>
<span>Normal</span>
</label>
<label class="flex items-center gap-2 text-gray-800">
<input
type="radio"
name="urgency"
value="blocking-my-work"
class="h-4 w-4 accent-blue-600"
required
>
<span>Blocking my work</span>
</label>
</div>
</fieldset>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-url" class="text-sm font-semibold text-gray-900">
Page where it happened
<span class="font-normal text-gray-500">(optional)</span>
</label>
<input
type="url"
id="fb-url"
name="url"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
placeholder="https://"
>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-steps" class="text-sm font-semibold text-gray-900">
What happened? Steps to reproduce
</label>
<textarea
id="fb-steps"
name="steps"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
required
rows="5"
></textarea>
</div>
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="fb-expected" class="text-sm font-semibold text-gray-900">
What did you expect to happen?
<span class="font-normal text-gray-500">(optional)</span>
</label>
<textarea
id="fb-expected"
name="expected"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-gray-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30"
rows="3"
></textarea>
</div>
<!-- Honeypot: keep it hidden. People never see it; bots fill it in and get flagged as spam. -->
<div
aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
>
<label for="fb-honey">Leave this field empty</label>
<input type="text" id="fb-honey" name="_honey" tabindex="-1" autocomplete="off">
</div>
<input type="hidden" name="_subject" value="New support ticket">
<button
type="submit"
class="min-h-11 justify-self-start rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-60 sm:col-span-2"
>
Send ticket
</button>
</form>
About this template
Most support threads start with a question back to the customer: which page, what did you expect, can you do it again? This form asks those up front. The steps-to-reproduce box is required; the expected-result box is optional but the one that turns a vague report into a fixable one.
Category and urgency are fixed choices so tickets can be triaged at a glance — in the email's field table, in a Slack message, or sorted in the CSV export. Urgency is three levels on purpose: with five, everything becomes “high”.
It is a form, not a helpdesk. Formboost stores the ticket and delivers it; your team answers from the tool you forward tickets to, or by replying to the notification — the HTML + JS and React versions set the customer's address as its reply-to.
Fields in this template
| Field | Name | Type | Required | Why it is there |
|---|---|---|---|---|
| Name | name | Short text | Yes | Required. |
email | Yes | Required, so you can reply and so Auto Reply can confirm receipt on Starter and above. | ||
| What is it about? | category | Dropdown | Yes | Required dropdown for routing. |
| How urgent is it? | urgency | Single choice | Yes | Required, three levels so the scale stays meaningful. |
| Page where it happened | url | Link (URL) | No | Optional link to the page with the problem. |
| What happened? Steps to reproduce | steps | Long text | Yes | Required. The single most useful thing in a bug report. |
| What did you expect to happen? | expected | Long text | No | Optional, and what turns “it doesn't work” into a fixable report. |
Each name becomes a column in your dashboard and a row in the notification email. The hidden _honey field is the honeypot; _subject sets the email's subject line. Both are reserved fields and never appear in the field table.
Make it yours
- Match the categories to the people who answer them, so each one has an owner.
- Add a hidden
planoraccountfield from your app's page template to skip the “which account?” question. - Send blocking tickets somewhere louder: a Telegram chat on the team's phones is one option.
Where the submissions go
- Tickets are stored in your dashboard and emailed to support, with the category and urgency in the field table.
- Post each ticket to a Discord or Slack channel so the team sees it without watching an inbox — one chat destination per form on Free.
- On Starter and above, open a ticket in your helpdesk through a webhook or Zapier.
Before you publish it
- Create a form in the Formboost dashboard and replace
YOUR_ENDPOINT_IDwith its ID. - Pick one variant: HTML posts and reloads, HTML + JS and React send JSON with
fetch()and stay on the page. - For the plain HTML version, add a hidden
_redirectwith anhttps://thank-you page on the same site as the form — anything else falls back to Formboost's page. - Submit it once from the live page and check the submission in your dashboard. Every request is acknowledged, so the dashboard is the real test. Troubleshooting covers the rest.
More form templates
Feedback form template
A rating, two open questions and the page it came from — short enough that people finish it.
Quote request form template
Service, budget range, timeline and the brief — enough to reply with a real number.
Appointment request form template
A preferred date and time, a second choice and the reason for the visit — a request you confirm, not a calendar.
All form templates
Every template, in HTML, React and Tailwind.
Put the support ticket form live
Free forever: 500 submissions a month across 10 forms, no card.