How to Build a Responsive Contact Form in WordPress with Bootstrap & PHP (2026 Guide)

Last Updated: January 20, 2026

Find us on google

Creating a custom contact form in WordPress doesn’t require expensive plugins. With Bootstrap for styling and PHP for processing, you can build a fully functional, responsive contact form that integrates seamlessly with your site—and you’ll understand exactly how it works.

This guide walks you through building a contact form from scratch, styling it with Bootstrap, handling form submissions, validating input, and sending emails. No bloat. No unnecessary plugins. Just clean, working code.


What You’ll Learn

By the end of this tutorial, you’ll have:

  • A fully responsive contact form styled with Bootstrap 5
  • Form validation (both frontend and backend)
  • Email notifications when users submit
  • Security protections (nonces, input sanitization)
  • A working example you can customize for any purpose
  • Understanding of how the code actually works

Estimated time: 30-45 minutes
Skill level: Beginner to intermediate (some PHP knowledge helpful but not required)
What you need: A WordPress site, access to your theme files or plugin editor


Part 1: Create the HTML Form with Bootstrap

First, we’ll create the HTML structure using Bootstrap 5’s form classes. This gives us a beautiful, mobile-responsive form without writing custom CSS.

Step 1: Create a Custom Page Template

In your WordPress admin:

  1. Go to Pages → Add New
  2. Title it “Contact Us” (or whatever you prefer)
  3. Open the page in your theme editor or create a custom template file

Create a file called page-contact.php in your theme directory (or child theme):

<?php
/**
 * Template Name: Contact Form Page
 */
get_header();
?>

<div class="container mt-5 mb-5">
    <div class="row">
        <div class="col-lg-8 mx-auto">
            <h1 class="mb-4">Contact Us</h1>
            
            <?php
            // Display success message if form was submitted
            if (isset($_GET['form_submitted']) && $_GET['form_submitted'] == 'success') {
                echo '<div class="alert alert-success alert-dismissible fade show" role="alert">
                        <strong>Thank you!</strong> Your message has been sent successfully.
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                      </div>';
            }
            ?>

            <form method="POST" action="<?php echo esc_url(admin_url('admin-ajax.php')); ?>" class="needs-validation" novalidate>
                
                <!-- Nonce field for security -->
                <?php wp_nonce_field('contact_form_nonce', 'contact_nonce'); ?>
                
                <!-- Name field -->
                <div class="mb-3">
                    <label for="name" class="form-label">Full Name</label>
                    <input type="text" class="form-control" id="name" name="name" placeholder="Enter your full name" required>
                    <div class="invalid-feedback">
                        Please provide your name.
                    </div>
                </div>

                <!-- Email field -->
                <div class="mb-3">
                    <label for="email" class="form-label">Email Address</label>
                    <input type="email" class="form-control" id="email" name="email" placeholder="your@email.com" required>
                    <div class="invalid-feedback">
                        Please provide a valid email address.
                    </div>
                </div>

                <!-- Phone field (optional) -->
                <div class="mb-3">
                    <label for="phone" class="form-label">Phone Number (Optional)</label>
                    <input type="tel" class="form-control" id="phone" name="phone" placeholder="+1 (555) 123-4567">
                </div>

                <!-- Subject field -->
                <div class="mb-3">
                    <label for="subject" class="form-label">Subject</label>
                    <input type="text" class="form-control" id="subject" name="subject" placeholder="What is this about?" required>
                    <div class="invalid-feedback">
                        Please provide a subject.
                    </div>
                </div>

                <!-- Message field -->
                <div class="mb-3">
                    <label for="message" class="form-label">Message</label>
                    <textarea class="form-control" id="message" name="message" rows="5" placeholder="Type your message here..." required></textarea>
                    <div class="invalid-feedback">
                        Please enter a message (at least 10 characters).
                    </div>
                </div>

                <!-- Submit button -->
                <button type="submit" name="submit_contact_form" class="btn btn-primary btn-lg w-100">
                    Send Message
                </button>

            </form>
        </div>
    </div>
</div>

<script>
// Bootstrap form validation
(function () {
    'use strict';
    window.addEventListener('load', function () {
        var forms = document.querySelectorAll('.needs-validation');
        Array.prototype.slice.call(forms).forEach(function (form) {
            form.addEventListener('submit', function (event) {
                if (!form.checkValidity()) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                form.classList.add('was-validated');
            }, false);
        });
    }, false);
})();
</script>

<?php get_footer(); ?>

What’s happening:

  • <form method="POST" action="..."> — Sends data to WordPress via AJAX
  • wp_nonce_field() — Security token to prevent unauthorized form submissions
  • Bootstrap classes like form-control, form-label, mb-3 — Styling and spacing
  • needs-validation — Triggers Bootstrap’s client-side validation
  • required — HTML5 validation (checked before PHP validation)

Part 2: Handle Form Processing with PHP

Now we’ll create the PHP script that processes the form, validates the data, and sends emails.

Add this code to your theme’s functions.php file:

<?php
/**
 * Handle Contact Form Submission via AJAX
 */
add_action('wp_ajax_nopriv_submit_contact_form', 'handle_contact_form_submission');
add_action('wp_ajax_submit_contact_form', 'handle_contact_form_submission');

function handle_contact_form_submission() {
    
    // Verify nonce for security
    if (!isset($_POST['contact_nonce']) || !wp_verify_nonce($_POST['contact_nonce'], 'contact_form_nonce')) {
        wp_send_json_error(['message' => 'Security check failed']);
        wp_die();
    }

    // Step 1: Collect and sanitize form data
    $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
    $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
    $phone = isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '';
    $subject = isset($_POST['subject']) ? sanitize_text_field($_POST['subject']) : '';
    $message = isset($_POST['message']) ? sanitize_textarea_field($_POST['message']) : '';

    // Step 2: Validate form data
    $errors = [];

    // Check required fields
    if (empty($name)) {
        $errors[] = 'Name is required';
    }
    
    if (empty($email)) {
        $errors[] = 'Email is required';
    } elseif (!is_email($email)) {
        $errors[] = 'Please provide a valid email address';
    }

    if (empty($subject)) {
        $errors[] = 'Subject is required';
    }

    if (empty($message)) {
        $errors[] = 'Message is required';
    } elseif (strlen($message) < 10) {
        $errors[] = 'Message must be at least 10 characters long';
    }

    // If there are errors, return them
    if (!empty($errors)) {
        wp_send_json_error(['message' => implode(', ', $errors)]);
        wp_die();
    }

    // Step 3: Prepare email content
    $admin_email = get_option('admin_email');
    
    $email_subject = 'New Contact Form Submission: ' . $subject;
    
    $email_body = "You have received a new contact form submission:\n\n";
    $email_body .= "Name: " . $name . "\n";
    $email_body .= "Email: " . $email . "\n";
    if (!empty($phone)) {
        $email_body .= "Phone: " . $phone . "\n";
    }
    $email_body .= "Subject: " . $subject . "\n\n";
    $email_body .= "Message:\n" . $message . "\n\n";
    $email_body .= "---\n";
    $email_body .= "This message was sent from your website: " . get_site_url();

    // Step 4: Send email to admin
    $headers = [
        'From: ' . $name . ' <' . $email . '>',
        'Reply-To: ' . $email,
        'Content-Type: text/plain; charset=UTF-8'
    ];

    $mail_sent = wp_mail($admin_email, $email_subject, $email_body, $headers);

    // Step 5: Send confirmation email to user
    $user_subject = 'We received your message';
    $user_body = "Hi " . $name . ",\n\n";
    $user_body .= "Thank you for contacting us. We received your message and will get back to you as soon as possible.\n\n";
    $user_body .= "Best regards,\n";
    $user_body .= get_bloginfo('name');

    wp_mail($email, $user_subject, $user_body);

    // Step 6: Return success response
    if ($mail_sent) {
        wp_send_json_success(['message' => 'Your message has been sent successfully']);
    } else {
        wp_send_json_error(['message' => 'Failed to send message. Please try again.']);
    }

    wp_die();
}

What this does:

  1. Verifies nonce — Ensures the request came from your form (security)
  2. Sanitizes input — Removes potentially harmful code from user input
  3. Validates data — Checks that required fields are filled and valid
  4. Sends emails — One to admin, one confirmation to user
  5. Returns response — Tells the form whether it succeeded or failed

Understanding the Security Functions:

sanitize_text_field()     // Removes HTML tags, slashes, etc.
sanitize_email()          // Ensures valid email format
sanitize_textarea_field() // Allows some HTML but removes dangerous code
is_email()                // WordPress function to validate email
wp_verify_nonce()         // Confirms the security token is valid

Part 3: Add AJAX Handling (Optional but Recommended)

To make the form submit without page refresh, add this to your page-contact.php template:

<script>
document.addEventListener('DOMContentLoaded', function() {
    const contactForm = document.querySelector('form');
    
    if (contactForm) {
        contactForm.addEventListener('submit', function(e) {
            e.preventDefault();

            const formData = new FormData(contactForm);
            formData.append('action', 'submit_contact_form');

            // Show loading state
            const submitBtn = contactForm.querySelector('button[type="submit"]');
            const originalText = submitBtn.textContent;
            submitBtn.disabled = true;
            submitBtn.textContent = 'Sending...';

            fetch('<?php echo esc_js(admin_url('admin-ajax.php')); ?>', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                submitBtn.disabled = false;
                submitBtn.textContent = originalText;

                if (data.success) {
                    // Show success message
                    alert(data.data.message);
                    contactForm.reset();
                    contactForm.classList.remove('was-validated');
                } else {
                    // Show error message
                    alert('Error: ' + data.data.message);
                }
            })
            .catch(error => {
                console.error('Error:', error);
                submitBtn.disabled = false;
                submitBtn.textContent = originalText;
                alert('An error occurred. Please try again.');
            });
        });
    }
});
</script>

This makes the form submit via AJAX (no page refresh) while still working with the PHP handler.


Part 4: Customize the Form for Your Needs

Add More Fields

To add a dropdown or additional field, follow this pattern:

<!-- Select field -->
<div class="mb-3">
    <label for="category" class="form-label">Category</label>
    <select class="form-select" id="category" name="category" required>
        <option value="">Select a category...</option>
        <option value="support">Support</option>
        <option value="sales">Sales</option>
        <option value="partnership">Partnership</option>
    </select>
    <div class="invalid-feedback">
        Please select a category.
    </div>
</div>

Then handle it in PHP:

$category = isset($_POST['category']) ? sanitize_text_field($_POST['category']) : '';

Change Email Recipients

Want to send to multiple people? In the PHP handler:

$admin_emails = [
    'admin@example.com',
    'manager@example.com',
    'support@example.com'
];

foreach ($admin_emails as $email) {
    wp_mail($email, $email_subject, $email_body, $headers);
}

Customize Email Template

Make the email HTML-formatted:

$headers = [
    'From: ' . $name . ' <' . $email . '>',
    'Reply-To: ' . $email,
    'Content-Type: text/html; charset=UTF-8'
];

$email_body = '<html><body>';
$email_body .= '<h2>New Contact Form Submission</h2>';
$email_body .= '<p><strong>Name:</strong> ' . esc_html($name) . '</p>';
$email_body .= '<p><strong>Email:</strong> ' . esc_html($email) . '</p>';
$email_body .= '<p><strong>Message:</strong></p>';
$email_body .= '<p>' . nl2br(esc_html($message)) . '</p>';
$email_body .= '</body></html>';

Part 5: Test Your Form

  1. Go to your contact page in the browser
  2. Try submitting with empty fields — Should show validation errors
  3. Submit with invalid email — Should show “Invalid email” error
  4. Submit a valid form — Check your email inbox (might be in spam)
  5. Check that user receives confirmation email

Common Issues & Fixes

Form doesn’t send:

  • Check that emails aren’t going to spam folder
  • Verify your site’s email configuration (ask your hosting provider)
  • Make sure the page is using the correct template

Validation not working:

  • Ensure Bootstrap CSS is loaded on the page
  • Check browser console for JavaScript errors

Emails not formatted correctly:

  • Use wp_mail() with proper headers
  • Test with a simple text email first, then add HTML

Part 6: Add Security & Spam Protection

Optional: Add reCAPTCHA

To prevent spam bots, add Google reCAPTCHA v3:

In your form HTML:

<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response">

In your template footer:

<script src="https://www.google.com/recaptcha/api.js?render=YOUR_RECAPTCHA_SITE_KEY"></script>
<script>
grecaptcha.ready(function() {
    grecaptcha.execute('YOUR_RECAPTCHA_SITE_KEY', {action: 'submit'}).then(function(token) {
        document.getElementById('g-recaptcha-response').value = token;
    });
});
</script>

In your PHP handler:

$recaptcha_secret = 'YOUR_RECAPTCHA_SECRET_KEY';
$recaptcha_token = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : '';

$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
$response = wp_remote_post($verify_url, [
    'body' => [
        'secret' => $recaptcha_secret,
        'response' => $recaptcha_token
    ]
]);

$result = json_decode(wp_remote_retrieve_body($response), true);

if ($result['score'] < 0.5) {
    wp_send_json_error(['message' => 'Failed security check']);
    wp_die();
}

Part 7: Store Submissions in Database (Optional)

Want to save submissions in your WordPress database? Add this to your PHP handler:

// After validation, before sending emails
global $wpdb;
$table_name = $wpdb->prefix . 'contact_submissions';

// Create table (run once)
$wpdb->query("
    CREATE TABLE IF NOT EXISTS $table_name (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(100),
        email VARCHAR(100),
        phone VARCHAR(20),
        subject VARCHAR(200),
        message LONGTEXT,
        submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        ip_address VARCHAR(45),
        status VARCHAR(20) DEFAULT 'new'
    )
");

// Insert submission
$wpdb->insert(
    $table_name,
    [
        'name' => $name,
        'email' => $email,
        'phone' => $phone,
        'subject' => $subject,
        'message' => $message,
        'ip_address' => $_SERVER['REMOTE_ADDR']
    ],
    ['%s', '%s', '%s', '%s', '%s', '%s']
);

Then you can view submissions from WordPress admin by adding a custom admin page (more advanced).


Troubleshooting Checklist

  • [ ] Form displays on the page
  • [ ] Bootstrap styles are applied (buttons look right, spacing is correct)
  • [ ] Form validation shows errors for empty required fields
  • [ ] Valid submission goes through without errors
  • [ ] Admin email is received
  • [ ] User confirmation email is received
  • [ ] No fatal PHP errors in debug.log
  • [ ] Nonce verification is working

Next Steps

Once your form is working:

  1. Style it further — Add custom CSS to match your brand
  2. Create multiple forms — Use different nonces and handlers for different purposes
  3. Add conditional logic — Show/hide fields based on user selections
  4. Integrate with CRM — Send submissions to Zapier, Make, or your CRM
  5. Add file uploads — Allow users to attach documents

The Complete Beginner’s Guide to WordPress Speed Optimization in 2025


Complete Code Reference

All files needed:

  1. page-contact.php — HTML form template with validation
  2. functions.php addition — PHP handler for processing form
  3. Optional AJAX script — For no-refresh submission

Copy, customize, deploy. Your form is ready.


contact form
Signature: Cebp83Sqocctx5Eh2Ypwb1Pmdeyqzl9+Udnhgpbkbkyt3Xah/36Mvdzsed3P3Fspwhxwrmgz2A7Cyrbrqrfxmiuueybm3M9Eu5Ksiolnewdcawi2Q0Bwl99F0K8Umcvvsqvdyssn9Y0Mpjbxorwgupihb5Yubxkampiqz3Eowqqjrfuulrape1W9/Yhsf/Bz7Wrijhag78Ybr57Iqicnu6Vl5Toitse52E4Czi0Crskyk8Nekylqc6/Orcs20F939Fjocnjb9Z+8Mlxy3Uvtpxg3Kf0Kol25R7Nf2Haec3/Z1Trv+Wmtabpghuvi8Sktwgau96Qaqsyazx4Symo5Tbiip64Chypabcyj67Fw818=

Leave a Comment

//
Our customer support team is here to answer your questions. Ask us anything!
👋 Hi, how can I help?