Back to blog
    By

    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.
    • _redirect sends visitors back to a thank-you page on your site. It must be https:// and on the same site as the form.
    • Testing from localhost lands 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.io or a project site at https://<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 sitehttps://<user>.github.io/thanks.html
    Project sitehttps://<user>.github.io/<repository>/thanks.html
    Custom domainhttps://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:

    1. Your dashboard. Every submission appears there, including any marked as spam.
    2. 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 from localhost, 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 _honey field 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 seeLikely causeFix
    Formboost's thank-you page instead of yoursTesting 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 dashboardThe endpoint ID is wrong or incompleteCopy the endpoint again from the dashboard
    In the dashboard, not in your inboxThe notification went to spam, or the submission was flagged as spamCheck your spam folder and the dashboard's spam tab
    415 in the browser consoleA script sent new FormData(form) as the bodySend JSON, as in the example above
    429 in the browser consoleToo many submissions from one IP in a minuteWait a minute and try again

    The troubleshooting guide covers the rest, including chat and webhook deliveries.

    Next steps

    Get your free Formboost endpoint →