How to Add a Contact Form to GitHub Pages

8 min read
Static Forms Team

GitHub Pages is an excellent platform for hosting static websites - it's free, fast, and integrates seamlessly with your Git workflow. However, one common challenge developers face is adding a functional contact form since GitHub Pages doesn't support server-side processing. The good news? You can easily add a professional contact form to your GitHub Pages site without writing any backend code.

In this comprehensive guide, we'll show you how to implement a fully functional contact form on GitHub Pages using Static Forms, a form backend service that handles all the server-side logic for you. Your visitors will be able to send messages, and you'll receive them directly in your email inbox.

Why GitHub Pages Needs a Form Solution

GitHub Pages serves only static files - HTML, CSS, and JavaScript. While this makes it incredibly fast and secure, it means you can't process form submissions directly. Traditional form handling requires:

  • Server-side code to process submissions
  • A database to store form data
  • Email sending capabilities
  • Spam protection mechanisms

Static Forms solves all of these challenges by providing a dedicated API endpoint that handles form submissions for you.

Prerequisites

Before we begin, make sure you have:

Step 1: Create a Basic HTML Contact Form

Let's start with a simple contact form. Create a new file called contact.html in your GitHub Pages repository, or add this form to an existing page:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contact Us</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .contact-container {
            background: white;
            padding: 40px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            margin-bottom: 10px;
        }
        .subtitle {
            color: #666;
            margin-bottom: 30px;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            color: #333;
            font-weight: 500;
        }
        input[type="text"],
        input[type="email"],
        textarea {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
            transition: border-color 0.3s;
        }
        input:focus,
        textarea:focus {
            outline: none;
            border-color: #4A90E2;
        }
        textarea {
            resize: vertical;
            min-height: 120px;
        }
        button {
            background-color: #4A90E2;
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #357ABD;
        }
        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
        .alert {
            padding: 15px;
            border-radius: 4px;
            margin-bottom: 20px;
            display: none;
        }
        .alert.show {
            display: block;
        }
        .alert-success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .alert-error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
    </style>
</head>
<body>
    <div class="contact-container">
        <h1>Get in Touch</h1>
        <p class="subtitle">We'd love to hear from you. Send us a message and we'll respond as soon as possible.</p>

        <div id="alertBox" class="alert"></div>

        <form id="contactForm" action="https://api.staticforms.dev/submit" method="POST">
            <div class="form-group">
                <label for="name">Your Name *</label>
                <input type="text" id="name" name="name" required>
            </div>

            <div class="form-group">
                <label for="email">Your Email *</label>
                <input type="email" id="email" name="email" required>
            </div>

            <div class="form-group">
                <label for="subject">Subject</label>
                <input type="text" id="subject" name="subject" placeholder="What's this about?">
            </div>

            <div class="form-group">
                <label for="message">Message *</label>
                <textarea id="message" name="message" required placeholder="Tell us what's on your mind..."></textarea>
            </div>

            <!-- Static Forms Configuration -->
            <input type="hidden" name="apiKey" value="YOUR_API_KEY_HERE">
            <input type="hidden" name="replyTo" value="@">
            <input type="text" name="honeypot" style="display:none">

            <button type="submit" id="submitBtn">Send Message</button>
        </form>
    </div>

    <script>
        const form = document.getElementById('contactForm');
        const submitBtn = document.getElementById('submitBtn');
        const alertBox = document.getElementById('alertBox');

        form.addEventListener('submit', async function(e) {
            e.preventDefault();

            // Disable submit button
            submitBtn.disabled = true;
            submitBtn.textContent = 'Sending...';

            // Hide any previous alerts
            alertBox.classList.remove('show', 'alert-success', 'alert-error');

            try {
                const formData = new FormData(form);
                const data = Object.fromEntries(formData);

                const response = await fetch('https://api.staticforms.dev/submit', {
                    method: 'POST',
                    body: JSON.stringify(data),
                    headers: { 'Content-Type': 'application/json' }
                });

                const result = await response.json();

                if (result.success) {
                    // Show success message
                    alertBox.textContent = 'Thank you! Your message has been sent successfully. We\'ll get back to you soon.';
                    alertBox.classList.add('alert-success', 'show');

                    // Reset form
                    form.reset();
                } else {
                    throw new Error(result.message || 'Something went wrong');
                }
            } catch (error) {
                // Show error message
                alertBox.textContent = 'Oops! Something went wrong. Please try again.';
                alertBox.classList.add('alert-error', 'show');
            } finally {
                // Re-enable submit button
                submitBtn.disabled = false;
                submitBtn.textContent = 'Send Message';
            }
        });
    </script>
</body>
</html>

Important: Replace YOUR_API_KEY_HERE with your actual API key from your Static Forms dashboard.

Step 2: Understanding the Form Configuration

Let's break down the key parts of the form:

The Action Attribute

HTML
<form action="https://api.staticforms.dev/submit" method="POST">

This points to the Static Forms API endpoint that will handle your submissions.

Required Hidden Fields

HTML
<input type="hidden" name="apiKey" value="YOUR_API_KEY_HERE">

Your API key authenticates your form with Static Forms.

Optional Configuration Fields

HTML
<input type="hidden" name="replyTo" value="@">

The @ value tells Static Forms to use the email address from the form's email field as the reply-to address, making it easy to respond to messages.

Honeypot Spam Protection

HTML
<input type="text" name="honeypot" style="display:none">

This hidden field catches bots that automatically fill out all form fields. Legitimate users won't see or fill this field.

Step 3: Add reCAPTCHA Protection (Optional)

For additional spam protection, you can add Google reCAPTCHA. First, get your reCAPTCHA keys from the Google reCAPTCHA Admin Console.

Add the reCAPTCHA script to your <head>:

HTML
<head>
    <!-- Other head elements -->
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>

Then add the reCAPTCHA widget to your form:

HTML
<div class="form-group">
    <div class="g-recaptcha" data-sitekey="YOUR_RECAPTCHA_SITE_KEY"></div>
</div>

Don't forget to configure your reCAPTCHA secret key in your Static Forms CAPTCHA settings.

For more details on implementing reCAPTCHA, check out our understanding reCAPTCHA guide.

Step 4: Deploy to GitHub Pages

Now let's deploy your contact form to GitHub Pages:

  1. Commit your changes:
Bash
git add contact.html
git commit -m "Add contact form with Static Forms integration"
  1. Push to GitHub:
Bash
git push origin main
  1. Wait for deployment: GitHub Pages automatically deploys your changes. This usually takes 1-2 minutes.

  2. Test your form: Visit https://yourusername.github.io/yourrepo/contact.html and submit a test message.

Step 5: Customize Your Form

Custom Success Redirect

Instead of showing a success message on the same page, you can redirect users to a thank-you page:

HTML
<input type="hidden" name="redirectTo" value="https://yourusername.github.io/yourrepo/thank-you.html">

Create a thank-you.html page:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Thank You</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        .thank-you-container {
            text-align: center;
            background: white;
            padding: 60px 40px;
            border-radius: 10px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
        }
        h1 {
            color: #333;
            margin-bottom: 20px;
        }
        p {
            color: #666;
            font-size: 18px;
            margin-bottom: 30px;
        }
        a {
            display: inline-block;
            padding: 12px 30px;
            background-color: #4A90E2;
            color: white;
            text-decoration: none;
            border-radius: 4px;
            transition: background-color 0.3s;
        }
        a:hover {
            background-color: #357ABD;
        }
    </style>
</head>
<body>
    <div class="thank-you-container">
        <h1>✅ Message Sent!</h1>
        <p>Thank you for contacting us. We'll get back to you as soon as possible.</p>
        <a href="/">Return to Home</a>
    </div>
</body>
</html>

Custom Email Subject

Set a custom subject line for better organization:

HTML
<input type="hidden" name="subject" value="New Contact from GitHub Pages Site">

Or use a visible subject field (as shown in the main example) to let users set their own subject.

Advanced Features

File Attachments

Allow users to upload files with their messages:

HTML
<div class="form-group">
    <label for="attachment">Attach File (Optional)</label>
    <input type="file" id="attachment" name="attachment" accept=".pdf,.doc,.docx,.jpg,.png">
</div>

Note: File uploads require encoding the form as multipart/form-data. See our file uploads guide for implementation details.

Custom Fields

Add custom fields by prefixing the name with $:

HTML
<div class="form-group">
    <label for="company">Company Name</label>
    <input type="text" id="company" name="$company">
</div>

Webhook Integration

For advanced workflows, you can connect your form to other services using webhooks (paid plans: Pro/Advanced). This allows you to:

  • Send submissions to Slack or Discord
  • Add contacts to your CRM
  • Trigger automation workflows
  • Store data in Google Sheets or Airtable

Configure webhooks in your Static Forms dashboard.

Complete Working Example

Here's a complete, copy-paste-ready contact form for GitHub Pages:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contact - My GitHub Pages Site</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about.html">About</a>
        <a href="/contact.html">Contact</a>
    </nav>

    <main>
        <section class="contact-section">
            <h1>Contact Us</h1>
            <p>Have a question or want to work together? Drop us a message!</p>

            <form action="https://api.staticforms.dev/submit" method="POST" id="contactForm">
                <input type="text" name="name" placeholder="Your Name" required>
                <input type="email" name="email" placeholder="Your Email" required>
                <input type="text" name="subject" placeholder="Subject">
                <textarea name="message" placeholder="Your Message" required></textarea>

                <!-- Static Forms Configuration -->
                <input type="hidden" name="apiKey" value="YOUR_API_KEY_HERE">
                <input type="hidden" name="replyTo" value="@">
                <input type="text" name="honeypot" style="display:none" tabindex="-1" autocomplete="off">

                <button type="submit">Send Message</button>
            </form>
        </section>
    </main>
</body>
</html>

Troubleshooting Common Issues

Not Receiving Email Notifications

If you're not receiving form submissions:

  • ✅ Verify your API key is correct in the form
  • ✅ Check your spam/junk folder
  • ✅ Confirm your email is verified in Static Forms
  • ✅ Test with different email addresses

Form Submission Fails

If submissions aren't working:

  • ✅ Check the browser console for JavaScript errors
  • ✅ Ensure the API endpoint URL is correct: https://api.staticforms.dev/submit
  • ✅ Verify all required fields have the required attribute
  • ✅ Make sure the honeypot field is properly hidden

CORS Errors

If you see CORS-related errors:

  • ✅ Use HTTPS for your GitHub Pages site (enable in repository settings)
  • ✅ Ensure you're using JSON format when submitting via JavaScript
  • ✅ Include proper Content-Type header: application/json

Best Practices for GitHub Pages Forms

  1. Keep it simple - Only ask for information you actually need
  2. Validate inputs - Use HTML5 validation and JavaScript for better UX
  3. Provide feedback - Show loading states and confirmation messages
  4. Mobile-friendly - Ensure your form works well on all devices
  5. Test thoroughly - Submit test forms before going live

Conclusion

Adding a contact form to your GitHub Pages site doesn't have to be complicated. With Static Forms, you can implement a professional contact form in minutes without any backend code or complex infrastructure.

Your form will handle submissions reliably, protect against spam with built-in security features, and deliver messages straight to your inbox - all while keeping your GitHub Pages site completely static.

Ready to add a contact form to your GitHub Pages site? Sign up for Static Forms and get started today with our free tier!

For more integration examples, check out our guides for Next.js, Gatsby, and Nuxt.js.