How to Add a Contact Form to GitHub Pages
GitHub Pages can't run server code, so a form needs somewhere to post. Add a working contact form with a thank-you page, spam protection and email alerts.
GitHub Pages can't process a form. GitHub describes it as a static site hosting service that publishes the HTML, CSS and JavaScript in your repository (GitHub Docs). Nothing runs when a visitor clicks Send, so the form has to post somewhere that does. That is what a form backend is for: you point the form's action at an endpoint, and the endpoint stores the message, screens it for spam and emails it to you.
Key takeaways
- A GitHub Pages site only serves files. The form stays plain HTML in your repository and posts to a Formboost endpoint.
- No GitHub Actions, no serverless function and no Jekyll plugin are needed.
_redirectsends visitors back to a thank-you page on your site. It must behttps://and on the same site as the form.- Testing from
localhostlands on Formboost's own thank-you page. That is expected; on your live site the redirect works.- The free plan covers 10 forms and 500 submissions a month, with email notifications and no card.
What you need
- A GitHub Pages site — a user site at
https://<user>.github.ioor a project site athttps://<user>.github.io/<repository>(the two URL forms GitHub documents), with or without a custom domain. - A free Formboost account. Sign up, create a form, and copy its endpoint. It looks like
https://formboost.app/f/YOUR_ENDPOINT_ID.
Step 1: Add the form
Create contact.html in your repository (or add the form to an existing page). Replace YOUR_ENDPOINT_ID with your form's ID:
1<form action="https://formboost.app/f/YOUR_ENDPOINT_ID" method="POST">
2 <label for="name">Name</label>
3 <input id="name" name="name" type="text" autocomplete="name" required>
4
5 <label for="email">Email</label>
6 <input id="email" name="email" type="email" autocomplete="email" required>
7
8 <label for="message">Message</label>
9 <textarea id="message" name="message" rows="5" required></textarea>
10
11 <!-- Honeypot: hidden from people; bots fill it in and get flagged as spam -->
12 <div aria-hidden="true" style="position:absolute;left:-9999px">
13 <input name="_honey" type="text" tabindex="-1" autocomplete="off">
14 </div>
15
16 <input type="hidden" name="_subject" value="New message from my GitHub Pages site">
17 <input type="hidden" name="_redirect" value="https://YOUR-USER.github.io/thanks.html">
18
19 <button type="submit">Send</button>
20</form>Each field's name becomes a line in the notification email and a column in your dashboard. The two hidden fields starting with an underscore are instructions, not answers: _subject sets the email's subject line and _redirect sets where the visitor goes afterwards. Neither appears in the email's list of fields.
Want different fields? The HTML form generator builds the form for you, and the contact form template has the same form in React and Tailwind.
Step 2: Add a thank-you page
Create thanks.html next to contact.html:
1<h1>Thanks — your message is on its way.</h1>
2<p>We'll reply within a couple of days. <a href="./">Back to the home page</a></p>Then point _redirect at it. The address depends on what kind of site you have:
| Your site | _redirect value |
|---|---|
| User site | https://<user>.github.io/thanks.html |
| Project site | https://<user>.github.io/<repository>/thanks.html |
| Custom domain | https://www.example.com/thanks.html |
Formboost follows the redirect only when it is https:// and on the same site as the page the form was submitted from. That rule is what stops anyone using the endpoint to send your visitors to a site of their choosing. The host has to match exactly: if your site answers on www.example.com, a redirect to example.com counts as a different site. Anything else — an http:// address, a different domain — falls back to Formboost's own thank-you page, so the submission is never lost.
GitHub serves github.io sites over HTTPS automatically, and sites with a custom domain support HTTPS enforcement (GitHub Docs). If you use a custom domain, turn on Enforce HTTPS in your repository's Pages settings so the thank-you page is served over HTTPS too.
Step 3: Publish and test
Commit both files and push. Once GitHub Pages has published the site, open contact.html on your live address, send yourself a message, and check two places:
- Your dashboard. Every submission appears there, including any marked as spam.
- Your inbox. The notification email lists each field. If it isn't there, check your spam folder once and mark it as not spam.
Two things that look like bugs but aren't:
- Testing from your computer lands on Formboost's thank-you page. Opening the file directly, or running
bundle exec jekyll serve, submits fromlocalhost, which is not the same site as your_redirect. The submission still arrives; only the redirect falls back. - A typo in the endpoint ID still "works" in the browser. The endpoint acknowledges every request straight away, before it looks the form up, so the browser can't tell you the ID was wrong. The dashboard can — which is why step 1 above is to look there.
The endpoint also limits each IP address to a handful of submissions a minute, so if you fire off a dozen test messages in a row, wait a minute before the next.
Optional: stay on the page instead of redirecting
If you'd rather show a message in place than load a thank-you page, submit with JavaScript. Send the fields as JSON — the endpoint accepts JSON or URL-encoded bodies, not the multipart body that new FormData(form) produces when you hand it straight to fetch():
1<p id="status" role="status" aria-live="polite"></p>
2
3<script>
4 const form = document.querySelector('form[action^="https://formboost.app/f/"]');
5 const status = document.getElementById("status");
6
7 form.addEventListener("submit", async (event) => {
8 event.preventDefault();
9 const data = Object.fromEntries(new FormData(form));
10 if (data.email) data._replyto = data.email; // replying to the email answers the visitor
11
12 try {
13 const response = await fetch(form.action, {
14 method: "POST",
15 headers: { "Content-Type": "application/json", Accept: "application/json" },
16 body: JSON.stringify(data),
17 });
18 status.textContent = response.ok
19 ? "Thanks — your message has been sent."
20 : "Something went wrong. Please try again.";
21 if (response.ok) form.reset();
22 } catch {
23 status.textContent = "Could not send. Check your connection and try again.";
24 }
25 });
26</script>A fetch() submission is answered with 202 Accepted and is never redirected, so you can delete the _redirect field for this version.
Using Jekyll?
Most GitHub Pages sites are built with Jekyll, and the form works the same way: it is HTML inside a page or an include, and needs no plugin. The Jekyll contact form guide shows how to keep the form in an _includes partial and store the endpoint in _config.yml, and the Jekyll reference covers the details.
Spam protection without a CAPTCHA
A public form on a public site will attract bots. Formboost handles that without asking your visitors to solve a puzzle:
- The honeypot. The hidden
_honeyfield in the example is invisible to people. Bots that fill in every input fill it in too, and the submission is kept but marked as spam and never forwarded. - Screening. Every submission is checked before it is delivered — heuristics on the Free plan, with AI deciding the borderline cases from Starter up. How the screening works.
- Rate limiting per IP address, so a script can't flood your inbox.
What not to collect
GitHub's own documentation says Pages sites "shouldn't be used for sensitive transactions like sending passwords or credit card numbers" (GitHub Docs). The same goes for any form you put on one. A contact form needs a name, an address and a message — nothing more. File uploads aren't accepted by the endpoint either; if you need a document, ask for a link to it.
Troubleshooting
| What you see | Likely cause | Fix |
|---|---|---|
| Formboost's thank-you page instead of yours | Testing from localhost, _redirect is http://, or its host differs from the form's (www. counts) | Test on the live site; use https:// and exactly the form's host |
| Nothing in the dashboard | The endpoint ID is wrong or incomplete | Copy the endpoint again from the dashboard |
| In the dashboard, not in your inbox | The notification went to spam, or the submission was flagged as spam | Check your spam folder and the dashboard's spam tab |
415 in the browser console | A script sent new FormData(form) as the body | Send JSON, as in the example above |
429 in the browser console | Too many submissions from one IP in a minute | Wait a minute and try again |
The troubleshooting guide covers the rest, including chat and webhook deliveries.
Next steps
- Contact forms for any static host — Cloudflare Pages, Netlify, Vercel and S3 work the same way.
- Generate a form with your own fields
- Form templates — enquiry, booking, RSVP, feedback and more
- Send submissions to Slack, Discord or Telegram as well as email