Contact Form Plugin
Features
Generator-Based Architecture: Create unlimited form configurations with dynamic field combinations without code changes
Automatic Notion Integration: Form submissions instantly saved to your Notion database with structured properties
Security-First Design: Built-in rate limiting (5 req/min), CAPTCHA verification, input sanitization, and XSS protection
Easy Integration: Copy-paste one script tag to embed into any website - works across all platforms and frameworks
Flexible Field Types: Support for text, email, phone, website, select dropdowns, textarea, and checkbox fields
Customizable Styling: Full theme control with custom colors, fonts, border radius, and responsive max-width
Client & Server Validation: Comprehensive validation on both frontend and backend for data integrity
Privacy Protected: Rate limiting prevents spam while CAPTCHA blocks automated bot submissions
Architecture Overview
System Components
┌───────────────────────────────────────────────────────────┐
│ USER'S WEBSITE │
│ ┌────────────────────────────────────────────────────┐ │
│ │ contact-widget.js (Generator) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ ContactWidget Instance │ │ │
│ │ │ - Configures form fields │ │ │
│ │ │ - Generates HTML dynamically │ │ │
│ │ │ - Handles user interactions │ │ │
│ │ │ - Validates inputs │ │ │
│ │ │ - Sends data to backend │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
│
│ HTTPS POST (JSON)
▼
┌────────────────────────────────────────────────────────────┐
│ NETLIFY BACKEND │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ /.netlify/functions/submit-contact │ │
│ │ - Rate limiting (5 req/min/IP) │ │
│ │ - CAPTCHA verification │ │
│ │ - Input sanitization │ │
│ │ - Email validation │ │
│ │ - Notion API integration │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│
│ Notion API
▼
┌─────────────────────────────────────────────────────────────┐
│ NOTION DATABASE │
│ - Stores contact submissions │
│ - Structured as database rows │
└─────────────────────────────────────────────────────────────┘Generator System
What is a Generator?
A generator in this application is an instance of the ContactWidget class that is configured with a specific set of form fields and UI options. Each generator produces a unique form based on its configuration.
Generator Configuration Pattern
new ContactWidget('#container', {
// API Configuration
apiUrl: 'https://your-domain.com/.netlify/functions/submit-contact',
// Theme Configuration
theme: {
primaryColor: '#3b82f6', // Button color
borderRadius: '12px', // Form border radius
fontFamily: 'sans-serif', // Font family
maxWidth: '500px' // Maximum width
},
// Form Configuration
form: {
title: 'Contact Us', // Form heading
submitText: 'Send Message', // Submit button text
successMessage: 'Thank you!', // Success message
errorMessage: 'Failed to send', // Error message
enableCaptcha: true, // Enable CAPTCHA
captchaSiteKey: 'your-site-key' // CAPTCHA site key
},
// Field Configuration (THE GENERATOR HEART)
fields: {
name: { enabled: true, required: true },
email: { enabled: true, required: true },
phone: { enabled: false },
company: { enabled: true, required: false },
subject: { enabled: true, required: true, type: 'select', options: [...] },
message: { enabled: true, required: true, type: 'textarea', rows: 4 },
website: { enabled: false },
budget: { enabled: false },
newsletter: { enabled: false }
}
});Core Components
1. ContactWidget Class (contact-widget.js)
Location: public/contact-widget.js (lines 133-620)
Responsibility: Core generator engine that:
- Accepts configuration objects
- Dynamically generates form HTML
- Manages form state and user interactions
- Handles client-side validation
- Communicates with backend API
Key Methods:
class ContactWidget {
constructor(container, options) // Initialize widget
mergeConfig(defaultConfig, userConfig) // Deep merge configs
init() // Setup widget
generateHTML() // Generate form DOM
generateFieldHTML(key, field) // Generate individual field
loadRecaptcha() // Load CAPTCHA script
bindEvents() // Attach event listeners
handleSubmit(e) // Process form submission
showStatus(message, isSuccess) // Display status messages
setLoading(isLoading) // Show/hide loading state
}Field Types Supported:
text- Standard text inputemail- Email input with validationtel- Phone number inputurl- Website URL inputtextarea- Multi-line text inputselect- Dropdown selectioncheckbox- Boolean checkbox
2. Backend Function (submit-contact.js)
Location: netlify/functions/submit-contact.js
Responsibility: Server-side processing including:
- Rate limiting (5 requests/minute per IP)
- CAPTCHA verification
- Input sanitization (XSS protection)
- Email validation
- Notion API integration
- Error handling and logging
Key Functions:
// Rate limiting
checkRateLimit(clientIP)
// CAPTCHA verification
verifyCaptcha(captchaToken, secretKey)
// Input sanitization
sanitizeInput(input)
// Main handler
export default async function handler(request, context)Notion Property Mapping:
{
name → Name (title)
email → Email (email)
phone → Phone (phone_number)
company → Company (rich_text)
subject → Subject (rich_text)
message → Message (rich_text)
website → Website (url)
budget → Budget (rich_text)
newsletter → Newsletter (select: Yes/No)
}3. Build Script (build-inject-env.js)
Location: Root directory
Responsibility: Injects environment variables at build time:
- Netlify site URL
- CAPTCHA site key
- Cache-busting timestamps
Process:
- Reads
NETLIFY_URLfrom environment - Reads
RECAPTCHA_SITE_KEYfrom environment - Injects into
contact-widget.js - Updates HTML references with cache-busting
- Updates sitemap.xml with site URL
Security Mechanisms
1. Rate Limiting
- 5 requests per minute per IP address
- In-memory storage (resets on serverless cold start)
- Prevents spam and DDoS attacks
2. CAPTCHA Protection
- Google reCAPTCHA v2 integration
- Client-side CAPTCHA widget
- Server-side verification
- Optional (can be disabled)
3. Input Sanitization
// Removes dangerous content:
- <script> tags
- <iframe> tags
- <object> and <embed> tags
- javascript: URLs
- Event handlers (onclick, etc.)4. Email Validation
- Client-side: Basic regex validation
- Server-side: Same regex for security
- Format:
^[^\s@]+@[^\s@]+\.[^\s@]+$
5. CORS Headers
- Allows requests from any origin
- Proper preflight handling
- Configured in netlify.toml
Build & Deployment
Build Process
npm run build
↓
node build-inject-env.js
↓
├── Read process.env.URL
├── Read process.env.RECAPTCHA_SITE_KEY
├── Inject into contact-widget.js
├── Update index.html with cache-busting
└── Update sitemap.xml with site URLDeployment
npm run deploy
↓
Netlify CLI detects:
├── netlify.toml configuration
├── Build command: npm run build
├── Publish directory: public
├── Functions directory: netlify/functions
└── Environment variables (set in Netlify dashboard)Required Environment Variables
Set in Netlify dashboard → Site settings → Environment variables:
NOTION_API_KEY=secret_xxx # Notion integration API key
NOTION_DATABASE_ID=abc123 # Notion database ID
RECAPTCHA_SITE_KEY=6Lcxxx # Google reCAPTCHA site key (optional)
RECAPTCHA_SECRET_KEY=6Lcxxx # Google reCAPTCHA secret (optional)How Generators Work
Example: Creating a Business Contact Form
// 1. Load the widget script
<script src="https://your-domain.com/contact-widget.js"></script>
// 2. Create a container
<div id="my-contact-form"></div>
// 3. Initialize the generator with business-focused configuration
<script>
new ContactWidget('#my-contact-form', {
form: {
title: 'Business Contact',
submitText: 'Submit Inquiry',
enableCaptcha: false
},
fields: {
name: { enabled: true, required: true },
email: { enabled: true, required: true },
company: { enabled: true, required: true },
phone: { enabled: true, required: false },
subject: {
enabled: true,
required: true,
type: 'select',
options: [
{ value: '', text: 'Select...', disabled: true, selected: true },
{ value: 'Partnership', text: 'Partnership' },
{ value: 'Sales', text: 'Sales Inquiry' }
]
},
budget: {
enabled: true,
required: false,
type: 'select',
options: [
{ value: '', text: 'Select...', disabled: true, selected: true },
{ value: '$1k-5k', text: '$1,000 - $5,000' },
{ value: '$5k-10k', text: '$5,000 - $10,000' }
]
},
message: { enabled: true, required: true },
newsletter: { enabled: true, required: false }
}
});
</script>Generator Execution Sequence
- Configuration Merge: User options merged with
DEFAULT_CONFIG - HTML Generation:
generateHTML()iterates through enabled fields - Field Rendering: Each field calls
generateFieldHTML() - DOM Injection: HTML is injected into container
- Event Binding: Form submit event attached
- CAPTCHA Loading: If enabled, loads Google reCAPTCHA
Implementing New Generators
Step 1: Add New Field Type to Widget
In contact-widget.js (add to generateFieldHTML method around line 344):
case 'date':
inputHTML = `
<input
type="date"
id="contact-${key}"
name="${key}"
${requiredAttr}
style="..."
>
`;
break;
case 'range':
inputHTML = `
<input
type="range"
id="contact-${key}"
name="${key}"
min="${field.min || 0}"
max="${field.max || 100}"
${requiredAttr}
style="..."
>
`;
break;Step 2: Update Backend to Handle New Field
In submit-contact.js (add to field mapping around line 194):
case 'date':
notionProperties["Event Date"] = {
date: { start: value }
};
break;
case 'range':
notionProperties["Score"] = {
number: parseInt(value)
};
break;Step 3: Create Example Usage
In index.html or examples.html:
new ContactWidget('#date-form', {
fields: {
name: { enabled: true, required: true },
email: { enabled: true, required: true },
eventDate: {
enabled: true,
required: true,
type: 'date',
label: 'Select Event Date'
},
guests: {
enabled: true,
required: false,
type: 'range',
label: 'Number of Guests',
min: 1,
max: 100
}
}
});Debugging Guide
Client-Side Debugging
Enable Console Logging:
new ContactWidget('#form', {
apiUrl: '...',
debug: true // Add this to see detailed logs
});Common Issues:
Form not rendering
- Check: Is script loaded? Check browser console for errors
- Check: Is container selector correct? (
#my-contact-form) - Check: DOM loaded? Wrap in
DOMContentLoadedlistener
Submission fails
- Check: API URL correct? Test in browser console:
fetch('url', {...}) - Check: CORS headers? Check Network tab in DevTools
- Check: Rate limit? Wait 1 minute and retry
- Check: API URL correct? Test in browser console:
CAPTCHA not loading
- Check: Site key is set? Check environment variables
- Check: Domain approved in Google reCAPTCHA admin
- Check: Browser console for script load errors
Backend Debugging
Check Netlify Function Logs:
# In terminal
netlify dev
# Or check Netlify dashboard:
# Site → Functions → submit-contact → View logsCommon Backend Issues:
"Notion configuration missing"
- Check:
NOTION_API_KEYis set in Netlify dashboard - Check:
NOTION_DATABASE_IDis set
- Check:
"Rate limit exceeded"
- Wait 1 minute, or increase
MAX_REQUESTS_PER_WINDOW
- Wait 1 minute, or increase
"CAPTCHA verification failed"
- Check:
RECAPTCHA_SECRET_KEYis set - Check: CAPTCHA token is being sent from client
- Check:
"Invalid email format"
- Email regex is strict:
^[^\s@]+@[^\s@]+\.[^\s@]+$ - Test with valid email like
test@example.com
- Email regex is strict:
Testing Examples
Test Basic Generator:
# Start local dev server
npm run dev
# Open browser to:
http://localhost:8888/index.html
http://localhost:8888/examples.html
# Check:
- Forms render correctly
- Submit shows loading spinner
- Success message appears
- Check Notion database for new entryExtension Roadmap
Adding New Features
1. Custom Validation Rules
// In contact-widget.js
fields: {
phone: {
enabled: true,
required: true,
validation: {
pattern: /^\+?[1-9]\d{1,14}$/,
message: 'Please enter a valid phone number'
}
}
}2. Conditional Field Display
// Show budget field only if subject is "Sales"
fields: {
budget: {
enabled: true,
showIf: { field: 'subject', equals: 'Sales' }
}
}3. File Upload Support
// In generateFieldHTML
case 'file':
inputHTML = `
<input type="file"
accept="${field.accept || '*/*'}"
max-size="${field.maxSize || 10485760}"
>
`;
break;4. Webhook Integration
// In submit-contact.js
const webhookUrl = process.env.WEBHOOK_URL;
if (webhookUrl) {
await fetch(webhookUrl, {
method: 'POST',
body: JSON.stringify(formData)
});
}5. Database Options
Support for multiple backends:
- Airtable
- Google Sheets
- Custom webhook
- Email notification
// In netlify.toml or environment
DATABASE_TYPE=notion|airtable|webhook|emailKey Takeaways for Developers
Understanding the Generator Pattern
- Every form is a generator instance - Each
new ContactWidget()creates a unique form - Configuration drives behavior - Fields, validation, and styling all from config
- Separation of concerns:
- Client: UI rendering and validation
- Backend: Security and data processing
- Notion: Data storage
Code Flow Quick Reference
User Input → Form Collection → Client Validation
→ API Request → Rate Limiting → CAPTCHA Verification
→ Input Sanitization → Notion API → Success/Error Response
→ UI UpdateCommon Configurations
Minimal Form:
{ name: true, email: true, message: true }Business Form:
{ name, email, company, phone, subject, message }Support Ticket:
{ name, email, subject (with options), message }Lead Capture:
{ name, email, newsletter: true }Environment Setup
Local Development
# 1. Clone repository
git clone <repo-url>
cd contact-us
# 2. Install dependencies
npm install
# 3. Create .env file (copy from env.example)
cp env.example .env
# 4. Edit .env with your credentials
NOTION_API_KEY=your_key
NOTION_DATABASE_ID=your_id
RECAPTCHA_SITE_KEY=your_key
RECAPTCHA_SECRET_KEY=your_secret
# 5. Run local dev server
npm run dev
# Opens on http://localhost:8888Production Deployment
# 1. Push to GitHub
git push origin main
# 2. Netlify auto-deploys (if connected)
# Or manually:
netlify deploy --prod
# 3. Set environment variables in Netlify dashboard
# Site settings → Environment variablesSummary
This project implements a generator-based contact form system where:
- Core Engine:
ContactWidgetclass generates forms dynamically - Generators: Different configurations create different form types
- Data Flow: Client → Netlify Function → Notion Database
- Security: Rate limiting, CAPTCHA, input sanitization
- Extensibility: Easy to add fields, validations, or backends
Key Files:
contact-widget.js- Generator engine (client-side)submit-contact.js- Backend processor (serverless function)build-inject-env.js- Build-time environment injection
Key Concepts:
- Configuration-driven form generation
- Serverless architecture for scalability
- Security-first approach
- Notion integration for data storage
This architecture makes it easy to create new form variations (generators) by simply changing the configuration object passed to ContactWidget, without modifying the core code.



