Guide

How to add a contact form to GitHub Pages

GitHub Pages serves static files and nothing else. If you point a form at your own site, GitHub answers with 405 Not Allowed, because there’s no server to receive the POST.

You have four options:

Option Trade-off
A mailto: link Opens the visitor’s email app, if they have one set up. Exposes your address to scrapers.
Embedding Google Forms Works, but it looks like Google Forms, not your site.
Your own serverless function Full control, but you build and run email, spam filtering and storage yourself.
A form backend Keep your own HTML; the backend receives the POST and emails you.

This guide uses a form backend, Formkove, which has a free plan with 300 submissions a month. The steps are the same for a plain HTML site and for Jekyll, which GitHub Pages builds by default.

1. Create the form endpoint

Sign up free, click New form, and copy the endpoint:

https://app.formkove.com/api/forms/YOUR_FORM_ID/submissions

Submissions go to your account email by default. To send them to another address, add it under Settings → Submission notifications and confirm it from the verification email.

2. Allow your GitHub Pages address

In the form’s Settings → Allowed Origins & Redirects, add the address your site runs on:

  • A user or organization site: https://USERNAME.github.io
  • A project site (https://USERNAME.github.io/REPO/): still https://USERNAME.github.io. Origins don’t include the path.
  • A custom domain: add it too, for example https://example.com.

This lets you redirect back to your own thank-you page and submit with JavaScript.

3. Add the form

For a plain HTML site, paste this into contact.html (or any page) and replace YOUR_FORM_ID and USERNAME:

<form action="https://app.formkove.com/api/forms/YOUR_FORM_ID/submissions?redirect=https://USERNAME.github.io/thanks.html" method="POST">
<label for="name">Name</label>
<input id="name" name="name" type="text" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Send</button>
</form>

Then create thanks.html with a short “Thanks, I’ll reply soon” message. The redirect must be a full URL on an address you allowed in step 2.

The email field becomes the reply-to address on the notification, so you can answer by replying to the email.

Jekyll sites

Put the form in an include so you can use it on any page. Create _includes/contact-form.html:

<form action="https://app.formkove.com/api/forms/YOUR_FORM_ID/submissions?redirect={{ '/thanks/' | absolute_url }}" method="POST">
<label for="name">Name</label>
<input id="name" name="name" type="text" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Send</button>
</form>

absolute_url builds the full address from url and baseurl in _config.yml, so it works for project sites under /REPO/. Set url there:

url: "https://USERNAME.github.io"
baseurl: "/REPO" # leave empty for a user site

Add the form to a page with {% include contact-form.html %}, and create thanks.md:

---
layout: page
title: Thanks
permalink: /thanks/
---
Thanks for your message. I'll reply soon.

Show a message without leaving the page

To keep visitors on the page, submit with fetch() instead. Remove ?redirect=… from the action and add:

<script>
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const res = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
form.innerHTML = res.ok
? "<p>Thanks! I'll reply soon.</p>"
: "<p>Something went wrong. Please email me instead.</p>";
});
</script>

This needs your GitHub Pages address under Allowed Origins (step 2), or the browser blocks the response.

Keep spam out

Every Formkove form gets rate limits and an AI spam filter, which holds spam in your dashboard instead of emailing it to you. A public contact form on GitHub Pages will attract bots, so add a honeypot as well:

  1. On the form’s AI & Spam page, set a honeypot field name, such as website_url.
  2. Add a hidden input with that name:
<input type="text" name="website_url" style="display:none" tabindex="-1" autocomplete="off">

For more, add Cloudflare Turnstile with your own keys, and list USERNAME.github.io (and your custom domain) as hostnames on the Turnstile widget. How to stop contact form spam explains what each layer catches.

Troubleshooting

  • 405 Not Allowed from GitHub: the form is posting to your own site. Check that action is the full Formkove URL.
  • You land on Formkove’s page instead of yours: the redirect address isn’t allowed, or isn’t a full URL. Check step 2, and use https://.
  • A CORS error in the console with fetch(): your GitHub Pages address isn’t under Allowed Origins.
  • No email arrives: check your spam folder for the first one, and confirm the recipient address if you added a new one. Submissions always appear in your dashboard either way.

More fixes are in troubleshooting. Using another generator? See the guides for Hugo, Jekyll and Eleventy and Astro.

← All posts