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/submissionsSubmissions 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/): stillhttps://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 siteAdd the form to a page with {% include contact-form.html %}, and create thanks.md:
---layout: pagetitle: Thankspermalink: /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:
- On the form’s AI & Spam page, set a honeypot field name, such as
website_url. - 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 Allowedfrom GitHub: the form is posting to your own site. Check thatactionis 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.