Building a Serverless Contact Form with Cloudflare Pages Functions and KV Store

Cloudflare Pages Functions and KV Store provide a powerful serverless solution for building contact forms without traditional backend infrastructure. This post details how to implement a secure, scalable contact form using these technologies.

Table of Contents

Why Cloudflare Pages Functions and KV Store?

Traditional contact form solutions often require:

  • Dedicated backend servers
  • Database infrastructure
  • Email server configuration
  • Complex deployment pipelines

Cloudflare’s serverless stack eliminates these requirements while providing:

  • Zero Infrastructure Management: No servers to maintain or scale
  • Global Edge Network: Code runs close to users worldwide
  • Built-in Security: DDoS protection, rate limiting, and CAPTCHA support
  • Cost Efficiency: Pay-per-request pricing with generous free tier

Architecture Overview

The solution consists of four main components:

  1. Static Frontend: HTML form hosted on Cloudflare Pages
  2. Pages Function: Serverless handler processing form submissions
  3. KV Store: Distributed key-value storage for submission data
  4. Turnstile CAPTCHA: Bot protection and spam prevention

Implementation Guide

The complete working example code is available on GitHub: ivandachev-examples/cloudflare-contact-form

Step 1: Setting Up Cloudflare Pages

First, create a new Cloudflare Pages project:

# Create project directory
mkdir contact-form-app
cd contact-form-app

# Initialize npm project
npm init -y

# Install dependencies
npm install -D wrangler

Step 2: Creating the Contact Form

Create a simple HTML form in public/index.html:

Loading code...

View on GitHub: index.html

Step 3: Implementing the Pages Function

Create the serverless handler in functions/api/contact.js. This implementation includes:

  • CORS support for cross-origin requests
  • Rate limiting (5 requests per minute per IP)
  • Configurable validation with environment variables
  • API endpoints - POST for submissions, GET for retrieval
  • Turnstile CAPTCHA verification for bot protection
Loading code...

View on GitHub: contact.js

Key features in the code:

  • CORS headers configuration with allowed origins
  • Rate limiting using KV with automatic expiration
  • Input validation with configurable min/max lengths
  • API key authentication supporting both query parameter and header
  • Turnstile token verification with server-side validation
  • Comprehensive error handling with appropriate HTTP status codes

Step 4: Configuring KV Store

Create a wrangler.jsonc configuration file with KV namespace binding and environment variables:

Loading code...

View on GitHub: wrangler.jsonc

The configuration includes:

  • KV namespace binding for storing submissions
  • Allowed origins for CORS
  • Configurable validation limits
  • Comments for setting up the API key secret

Create the KV namespace and set up authentication:

# Create KV namespace using npm script
npm run kv:create
# Copy the generated ID to wrangler.jsonc

# Generate a secure API key
openssl rand -hex 32

# Set the API key as a secret
npx wrangler pages secret put API_KEY --project-name contact-form-app
# Enter the generated key when prompted

# Verify the secret was uploaded
npx wrangler pages secret list --project-name contact-form-app

Step 5: Configure Cloudflare Turnstile

Turnstile provides CAPTCHA protection to prevent spam and bot submissions:

  1. Create a Turnstile site in your Cloudflare dashboard:
    • Navigate to Turnstile in the sidebar
    • Click Add Site
    • Configure your site with your domain(s)
    • Choose widget mode (Managed or Invisible)
    • Copy the Site Key and Secret Key
  2. Update your configuration:
    • Set TURNSTILE_SITE_KEY in wrangler.jsonc
    • Update the site key in your HTML form
  3. Set the secret key:

    npx wrangler pages secret put TURNSTILE_SECRET_KEY --project-name contact-form-app
    # Enter your Turnstile secret key when prompted
    
  4. Widget customization options:
    <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-theme="light" data-size="normal"></div>
    

For detailed Turnstile setup instructions, see the TURNSTILE_SETUP.md guide in the example repository.

Deployment

Deploy your contact form application:

# Login to Cloudflare
wrangler login

# Deploy to Cloudflare Pages
npm run deploy

For local development:

# Run development server on http://localhost:8788
npm run dev

The example repository also includes development tools for code quality:

# Format code with Prettier
npm run format

# Run ESLint for JavaScript linting
npm run lint

# Run Stylelint for CSS linting
npm run lint:css

Cost Analysis

Cloudflare’s pricing model for this solution:

  • Pages: Free for unlimited requests
  • Functions: 100,000 requests/day free, then $0.50/million
  • KV Storage: 100,000 reads/day free, 1,000 writes/day free
  • KV Operations: $0.50/million reads, $5.00/million writes after free tier

For a typical contact form receiving 1,000 submissions per month:

  • Monthly Cost: $0 (within free tier)
  • Scale Capacity: Can handle millions of submissions without infrastructure changes

API Endpoints

The contact form provides these endpoints:

Submit Contact Form

  • POST /api/contact
  • Body: JSON with name, email, message, and cf-turnstile-response fields
  • Requires valid Cloudflare Turnstile token
  • Rate limited to 5 submissions per minute per IP

Get Submissions (requires API key)

  • GET /api/contact?api_key=YOUR_API_KEY
  • GET /api/contact with header X-API-Key: YOUR_API_KEY
  • Query parameters:
    • limit: Number of results per page (1-1000, default: 100)
    • cursor: Pagination cursor for next page
  • Returns JSON response with:
    • submissions: Array of submission objects
    • list_complete: Boolean indicating if there are more results
    • cursor: Cursor for next page (null if no more results)
    • count: Number of submissions in current page

Check API Status

  • GET /api/contact
  • Returns API status (no authentication required)

Security Best Practices

  1. CAPTCHA Protection: Cloudflare Turnstile prevents bot submissions
  2. Input Validation: Always validate and sanitize user input
  3. Rate Limiting: Implement per-IP rate limiting (5 requests/minute)
  4. CORS: Configure appropriate CORS headers for allowed domains
  5. API Security: Use API keys for data access endpoints
  6. Encryption: Use HTTPS for all communications
  7. Data Retention: Implement automatic data expiration policies
  8. Token Security: Turnstile tokens expire after 5 minutes and are single-use

Conclusion

Cloudflare Pages Functions and KV Store provide a robust, scalable solution for contact forms that eliminates traditional infrastructure complexity. This serverless approach offers:

  • Global Performance: Edge deployment ensures low latency worldwide
  • Automatic Scaling: Handle traffic spikes without configuration
  • Cost Efficiency: Pay only for what you use
  • Developer Experience: Simple deployment and maintenance

The combination of Pages Functions for compute and KV Store for persistence creates a complete backend solution that’s perfect for contact forms and similar applications requiring simple data collection and storage.


Have you built serverless applications with Cloudflare? Share your experiences and tips in the comments below!