Skip to content

ezcontactform/widget

EZCONTACTFORM Widget

A lightweight, embeddable contact form widget for websites. Easily add beautiful, customizable contact forms to any website with just a few lines of code.

Table of Contents

Features

  • πŸš€ Easy Integration - Add forms to your site with a single script tag
  • 🎨 Customizable Themes - Light, dark, and system theme support
  • πŸ“± Fully Responsive - Works perfectly on all devices
  • βœ… Client-side Validation - Real-time form validation
  • 🌍 International Support - Phone number formatting for 40+ countries
  • πŸ“Ž File Uploads - Support for file attachments with drag-and-drop
  • ⭐ Rich Field Types - Text, email, phone, file uploads, ratings, rich text editor, and more
  • πŸ”” Custom Callbacks - Success and error handlers for custom logic
  • β™Ώ Accessible - WCAG compliant with proper ARIA labels

Getting Started

Before you can use the widget, you'll need an EZCONTACTFORM account:

  1. Create an account at www.ezcontactform.com
  2. Create a form in your dashboard
  3. Get your Source ID from the form settings
  4. Add the widget to your website using the code examples below

The widget is free to use and open source. You only need an EZCONTACTFORM account to manage your forms and receive submissions.

Quick Start

Simple Embed

Add this to your HTML:

<div id="ezform-container"></div>
<script src="https://cdn.ezcontactform.com/widget.js" 
        data-source-id="YOUR_SOURCE_ID"></script>

Note: See CDN URLs section for versioned URLs and fallback options.

With Theme

<div id="ezform-container"></div>
<script src="https://cdn.ezcontactform.com/widget.js" 
        data-source-id="YOUR_SOURCE_ID"
        data-theme="dark"></script>

With Custom Container

<div id="my-custom-form"></div>
<script src="https://cdn.ezcontactform.com/widget.js" 
        data-source-id="YOUR_SOURCE_ID"
        data-container="my-custom-form"></script>

Advanced Usage (with callbacks)

<div id="ezform-container"></div>
<script>
  (function(w,d,s,f) {
    w.EZForm=w.EZForm||{};
    w.EZForm.forms=w.EZForm.forms||[];
    w.EZForm.forms.push({
      sourceId: 'YOUR_SOURCE_ID',
      container: 'ezform-container',
      options: {
        theme: 'light',
        metadata: { campaign_id: 'abc123' },
        onSuccess: function(response) {
          console.log('Form submitted:', response);
        },
        onError: function(error) {
          console.error('Error:', error);
        }
      }
    });
    if(!w.EZForm.loaded) {
      var js=d.createElement(s);
      js.src=f;
      js.async=true;
      d.getElementsByTagName('head')[0].appendChild(js);
      w.EZForm.loaded=true;
    }
  })(window,document,'script','https://cdn.ezcontactform.com/widget.js');
</script>

Installation

Option 1: CDN (Recommended)

Use the hosted version from our CDN. We provide both versioned URLs (for production stability) and latest URLs (for development).

CDN URLs

Primary CDN (Cloudflare):

  • JavaScript: https://cdn.ezcontactform.com/widget.js (latest)
  • CSS: https://cdn.ezcontactform.com/widget.css (latest)
  • Versioned JavaScript: https://cdn.ezcontactform.com/widget-{version}.js (e.g., widget-1.4.0.js)
  • Versioned CSS: https://cdn.ezcontactform.com/widget-{version}.css (e.g., widget-1.4.0.css)

Fallback CDN:

  • JavaScript: https://app.ezcontactform.com/widget.js (latest)
  • CSS: https://app.ezcontactform.com/widget.css (latest)
  • Versioned JavaScript: https://app.ezcontactform.com/widget-{version}.js
  • Versioned CSS: https://app.ezcontactform.com/widget-{version}.css

Usage Examples

Latest version (recommended for development):

<script src="https://cdn.ezcontactform.com/widget.js"></script>
<link rel="stylesheet" href="https://cdn.ezcontactform.com/widget.css">

Pinned version (recommended for production):

<script src="https://cdn.ezcontactform.com/widget-1.4.0.js"></script>
<link rel="stylesheet" href="https://cdn.ezcontactform.com/widget-1.4.0.css">

Note: Versioned URLs are immutable and cached forever, while latest URLs are cached for 1 hour. For production, we recommend using versioned URLs to ensure stability.

Option 2: Self-hosted

  1. Download widget.js and widget.css from this repository
  2. Host them on your server
  3. Update the script source in your HTML

Configuration

Script Tag Attributes

  • data-source-id - Required. Your source ID from the admin panel
  • data-theme - Theme: light, dark, or system (default: light)
  • data-version - Pin to a specific version (default: latest)
  • data-container - Custom container element ID (default: ezform-container or auto-generated)
  • data-disable-styles - Set to true to use your own CSS
  • data-enable-logging - Set to true to enable console logging
  • data-container - Custom container ID (default: auto-generated or ezform-container)

Container ID

The container ID is the HTML element ID where the form will be rendered.

Default behavior:

  • If you have a <div id="ezform-container"></div> before the script tag, it will use that
  • Otherwise, the widget creates a container automatically with ID ezform-{sourceId}

Custom container:

<div id="my-custom-form-container"></div>
<script src="https://cdn.ezcontactform.com/widget.js" 
        data-source-id="YOUR_SOURCE_ID"
        data-container="my-custom-form-container"></script>

Programmatic usage:

EZForm.renderForm({
  sourceId: 'YOUR_SOURCE_ID',
  container: 'my-custom-form-container',  // Container ID
  options: {}
})

Options Object (Advanced)

The options object allows fine-grained control over form behavior:

{
  // Theme Configuration
  theme: 'light',              // 'light', 'dark', or 'system' (default: 'light')
                               // 'system' auto-detects user's OS preference
  
  // Styling
  disableStyles: false,        // Set to true to use your own CSS
  customCSS: '',               // Custom CSS string to inject
  
  // Logging
  enableLogging: false,        // Enable console logging for debugging
  
  // Messages
  successMessage: 'Custom message',  // Override default success message
  
  // Custom Metadata
  metadata: {                   // Custom metadata passed with submission
    campaign_id: 'abc123',     // Tracking campaign ID
    source: 'homepage',        // Source page identifier
    user_id: 'user123',        // User identifier
    // Any custom key-value pairs
  },
  
  // Callbacks
  onSuccess: function(response) {
    // Called when form submits successfully
    // response: { id, status, message, ... }
    console.log('Form submitted:', response);
  },
  
  onError: function(error) {
    // Called when form submission fails
    // error: { message, ... }
    console.error('Submission error:', error);
  }
}

Metadata

Custom metadata can be passed with form submissions for tracking and analytics. Your custom metadata is stored in metadata.client on the server and can be used for filtering, reporting, and integrations.

Passing Custom Metadata

Via Options Object (Recommended):

EZForm.renderForm({
  sourceId: 'abc123',
  container: 'ezform-container',
  options: {
    metadata: {
      campaign_id: 'summer-2024',
      source: 'homepage',
      user_segment: 'premium'
    }
  }
})

Via Script Tag (using hidden field):

<div id="ezform-container"></div>
<input type="hidden" name="metadata" value='{"campaign_id":"summer-2024","source":"homepage"}' />
<script src="https://cdn.ezcontactform.com/widget.js" 
        data-source-id="YOUR_SOURCE_ID"></script>

How it works:

  • Metadata passed via options.metadata is automatically wrapped in metadata.client before sending to the API
  • The widget sends metadata as a separate field in the submission payload: { data: {...}, metadata: { client: {...} } }
  • Server stores your custom metadata in metadata.client for easy querying

Metadata Value Types

Supported types:

  • string - Text values
  • number - Numeric values
  • boolean - True/false values
  • object - Nested objects
  • array - Arrays of strings, numbers, or objects
  • null - Null values

Limitations:

  • Maximum payload size: 256KB per field (data and metadata combined)
  • Maximum keys: 100 keys per object
  • No functions or circular references

Examples

Simple metadata:

metadata: {
  campaign_id: 'summer-2024',
  source: 'homepage',
  referrer: 'google',
  user_id: 'user123'
}

Nested metadata:

metadata: {
  tracking: {
    campaign: 'summer-2024',
    ad_group: 'banner-ads',
    keyword: 'contact-form',
    creative: 'banner-728x90'
  },
  user: {
    segment: 'premium',
    cohort: '2024-Q1',
    lifetime_value: 1500
  },
  page: {
    url: '/contact',
    title: 'Contact Us',
    section: 'footer'
  }
}

With arrays:

metadata: {
  tags: ['marketing', 'lead-gen', 'contact'],
  visited_pages: ['/about', '/pricing', '/contact'],
  interests: [
    { category: 'technology', score: 0.8 },
    { category: 'business', score: 0.6 }
  ]
}

Complex nested structure:

metadata: {
  marketing: {
    channel: 'email',
    campaign: {
      id: 'summer-sale',
      variant: 'A',
      test_group: 'control'
    }
  },
  analytics: {
    page: {
      url: '/contact',
      section: 'footer',
      viewport: 'desktop'
    },
    user: {
      segment: 'premium',
      cohort: '2024-Q1'
    }
  }
}

Complete Metadata Structure

The server receives and stores metadata in this structure:

{
  "metadata": {
    "client": {
      "campaign_id": "summer-2024",
      "source": "homepage",
      "user_segment": "premium"
    },
    "timing": {
      "start_time": 1234567890,
      "end_time": 1234567900
    },
    "localization": {
      "locale": "en-US",
      "timezone": "America/New_York"
    },
    "browser": {
      "window_width": 1920,
      "window_height": 1080,
      "screen_width": 1920,
      "screen_height": 1080,
      "device_type": "desktop"
    },
    "source": {
      "id": "source-uuid",
      "source_key": "abc123",
      "version": 1,
      "pinned_version": 1
    },
    "widget": {
      "version": "1.4.0"
    },
    "spam_protection": {
      "blocked_by": null,
      "rate_limit_remaining": 29
    },
    "request": {
      "ip_address": "192.168.1.1",
      "user_agent": "Mozilla/5.0...",
      "referer": "https://example.com",
      "headers": {}
    },
    "utm_params": {
      "utm_source": "google",
      "utm_medium": "cpc",
      "utm_campaign": "summer-2024"
    },
    "referrer": "https://google.com",
    "landing_page": "/contact",
    "confirmation_acceptances": {
      "tos": "2024-12-07T16:00:00Z"
    },
    "custom": {
      "source_level_metadata": "value"
    }
  }
}

Automatic Metadata Capture

The following metadata is always captured automatically:

  • timing - Form fill duration (start_time and end_time in milliseconds)
  • localization - User locale (e.g., "en-US") and timezone (e.g., "America/New_York")
  • source - Source identification (id, source_key, version)
  • widget - Widget version (e.g., "1.4.0") - identifies which widget version created the submission
  • request - Server-side request info (IP address, user agent, referer)
  • spam_protection - Spam protection results

The following metadata is conditionally captured:

  • browser - Browser details (only if enabled in source settings)
    • window_width, window_height
    • screen_width, screen_height
    • device_type (desktop, mobile, tablet)
    • browser_name, browser_version
    • os_name, os_version

Using Metadata in Queries

Once stored, you can query entries by metadata:

// Example: Filter entries by campaign
// In your backend/admin panel, you can filter by:
metadata.client.campaign_id = 'summer-2024'

// Or query nested metadata:
metadata.client.tracking.campaign = 'summer-2024'

Size Limits and Validation

The API enforces the following limits to prevent abuse:

  • Maximum payload size: 256KB per field (data and metadata combined)
  • Maximum keys: 100 keys per object
  • Validation: Invalid or oversized payloads will return a 400 error

Example error response:

{
  "error": "Payload too large",
  "success": false
}

Best Practices

  1. Use consistent keys - Use the same metadata keys across forms for easier reporting
  2. Keep it simple - Avoid deeply nested structures (max 3-4 levels recommended)
  3. Size limits - Keep individual metadata objects under 10KB (well below the 256KB limit)
  4. No PII - Don't include personally identifiable information in metadata (use form fields instead)
  5. Use for tracking - Metadata is ideal for:
    • Campaign tracking (UTM parameters, campaign IDs)
    • A/B testing (variant IDs, test groups)
    • Analytics (page context, user segments)
    • Integration data (external system IDs, sync flags)
  6. Query-friendly - Structure metadata for easy filtering and reporting in your admin panel

Real-World Examples

Marketing campaign:

metadata: {
  campaign: {
    id: 'summer-2024',
    channel: 'email',
    variant: 'A',
    test_group: 'control'
  },
  utm: {
    source: 'newsletter',
    medium: 'email',
    campaign: 'summer-sale',
    term: 'contact-form',
    content: 'banner-top'
  }
}

Multi-step form tracking:

metadata: {
  form: {
    step: 3,
    total_steps: 5,
    completion_percentage: 60,
    started_at: '2024-12-07T16:00:00Z'
  },
  user: {
    session_id: 'session-xyz789',
    previous_interactions: 2
  }
}

Integration tracking:

metadata: {
  integration: {
    crm_id: 'crm-12345',
    sync_status: 'pending',
    external_system: 'salesforce'
  },
  workflow: {
    trigger_id: 'workflow-abc',
    step: 'form_submission',
    pipeline: 'lead-nurture'
  }
}

Field Types

The widget supports a wide variety of field types:

  • Text - Single-line text input
  • Email - Email validation
  • Phone - Phone number with international support
  • Textarea - Multi-line text input
  • Select - Dropdown selection
  • Checkbox - Single checkbox
  • Radio - Radio button group
  • File Upload - File attachments with drag-and-drop
  • Rating - Star rating (1-5)
  • Rich Text - Markdown editor with preview
  • Confirmation - Terms of service/privacy policy acceptance
  • Date/Time - Date and time pickers
  • Country/State - Location selectors

Examples

See widget-example.html for complete examples including:

  • Simple embed
  • Dark theme
  • Callbacks
  • Multiple forms on one page
  • Custom styling

Development

Local Development

  1. Clone this repository
  2. Serve the files locally:
# Python
python -m http.server 3000

# Node.js
npx http-server -p 3000

# PHP
php -S localhost:3000
  1. Open widget-example.html in your browser

Testing

To test the widget locally:

  1. Get a source ID:

    • Create a free account at www.ezcontactform.com
    • Create a form in your dashboard
    • Copy your Source ID from the form settings
  2. Configure allowed domains:

    • In your EZCONTACTFORM dashboard, add localhost to your allowed domains
    • This allows the widget to work during local development
  3. Update the example:

    • Open src/widget-example.html
    • Replace YOUR_SOURCE_ID with your actual Source ID
    • Serve locally and test

Note: For production use, make sure to add your actual domain to the allowed domains list in your EZCONTACTFORM dashboard.

Building

Prerequisites

  • Node.js 14+ and npm

Build Commands

# Install dependencies
npm install

# Build from package.json version
npm run build

# Build from Git tag
npm run build:tag

# Build with specific version
npm run build:version 1.4.0

Build Output

The build process generates versioned distribution files in the dist/ directory:

  • widget-{version}.js - Versioned JavaScript file (immutable)
  • widget-{version}.css - Versioned CSS file (immutable)
  • widget.js - Latest JavaScript file (points to current version)
  • widget.css - Latest CSS file (points to current version)

Version Management

The build script automatically:

  • Updates version in widget.js header comment
  • Updates version constant in the code
  • Generates versioned filenames
  • Creates latest symlinks/copies

Release Process

  1. Update version:

    npm version patch  # or minor, major
  2. Build and test locally:

    npm run build
    # Test dist/widget.js in browser
  3. Create Git tag and push:

    git tag v1.4.0
    git push origin v1.4.0
  4. Automated release:

    • GitHub Actions automatically builds on tag push
    • Creates GitHub Release with artifacts
    • Deploys to CDN (if configured)

File Structure

widget/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ widget.js          # Source file
β”‚   β”œβ”€β”€ widget.css         # Styles
β”‚   └── widget-example.html # Examples
β”œβ”€β”€ dist/                  # Generated files (gitignored)
β”‚   β”œβ”€β”€ widget-1.4.0.js   # Versioned
β”‚   β”œβ”€β”€ widget-1.4.0.css
β”‚   β”œβ”€β”€ widget.js          # Latest
β”‚   └── widget.css
β”œβ”€β”€ build.js               # Build script
β”œβ”€β”€ package.json           # Version management
└── README.md

Contributing

We welcome contributions! Please see our Contributing Guidelines and Code of Conduct for details on how to get involved.

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)
  • Mobile browsers (iOS Safari, Chrome Mobile)

API Reference

EZForm Object

The widget exposes a global EZForm object with the following methods:

EZForm.init()

Initialize and render all forms on the page. Called automatically on page load, but can be called manually if needed.

EZForm.init()

EZForm.renderForm(config)

Render a form programmatically.

Parameters:

  • config.sourceId (string, required) - Your source ID
  • config.container (string, required) - Container element ID where form will render
  • config.version (string, optional) - Version to use ('latest', specific number, or '2.x')
  • config.options (object, optional) - Options object (see Options Object section)

Example:

EZForm.renderForm({
  sourceId: 'abc123',
  container: 'my-form-container',
  version: 'latest',
  options: {
    theme: 'dark',
    metadata: { campaign: 'summer-2024' },
    onSuccess: function(response) {
      console.log('Success!', response);
    }
  }
})

EZForm.fetchConfig(sourceId, version)

Fetch form configuration from the API.

Parameters:

  • sourceId (string, required) - Your source ID
  • version (string, optional) - Version to fetch ('latest', specific number, or '2.x')

Returns: Promise resolving to form configuration object

Example:

const config = await EZForm.fetchConfig('abc123', 'latest');
console.log('Form config:', config);

EZForm.log(level, config, ...args)

Log messages (respects enableLogging setting).

Parameters:

  • level (string) - Log level: 'log', 'warn', 'error'
  • config (object) - Form configuration
  • ...args - Additional arguments to log

Example:

EZForm.log('log', config, 'Form loaded successfully');

EZForm.escapeHtml(text)

Escape HTML to prevent XSS attacks.

Parameters:

  • text (string) - Text to escape

Returns: Escaped HTML string

Example:

const safe = EZForm.escapeHtml('<script>alert("xss")</script>');
// Returns: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

EZForm.parseMarkdown(text)

Parse markdown text to HTML.

Parameters:

  • text (string) - Markdown text

Returns: HTML string

Example:

const html = EZForm.parseMarkdown('**Bold** text');
// Returns: "<strong>Bold</strong> text"

Events

The widget dispatches custom events you can listen to:

ezform:auto-open

Dispatched when a form should auto-open (based on auto_open_delay setting).

document.addEventListener('ezform:auto-open', function(event) {
  const sourceId = event.detail.sourceId;
  // Show modal or scroll to form
  console.log('Auto-open form:', sourceId);
});

Event detail:

  • sourceId (string) - The source ID that should auto-open

Callback Functions

onSuccess(response)

Called when form submission succeeds.

Parameters:

  • response (object) - Server response containing:
    • id (string) - Entry ID
    • status (string) - Status (usually 'success')
    • message (string) - Success message

Example:

options: {
  onSuccess: function(response) {
    console.log('Entry ID:', response.id);
    // Redirect to thank you page
    window.location.href = '/thank-you';
  }
}

onError(error)

Called when form submission fails.

Parameters:

  • error (object) - Error object containing:
    • message (string) - Error message
    • status (number, optional) - HTTP status code
    • details (object, optional) - Additional error details

Example:

options: {
  onError: function(error) {
    console.error('Submission failed:', error.message);
    // Show custom error message
    alert('Sorry, there was an error. Please try again.');
  }
}

Security

We take security seriously. Please see our Security Policy for reporting vulnerabilities and a list of built-in security features.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Need help? We're here for you:

For widget-specific issues or feature requests, please use GitHub Issues. For account-related questions or general support, visit our Help Center.

Changelog

Version 1.4.0

  • Added international phone support
  • Added file upload with drag-and-drop
  • Added rich text editor
  • Added confirmation modal
  • Improved accessibility
  • Dark theme improvements

Version 1.2.0

  • Version pinning support
  • Metadata structure updates
  • Browser details capture (optional)

Version 1.0.0

  • Initial release
  • Basic form rendering
  • Light/dark themes
  • Client-side validation

Made with ❀️ by the EZCONTACTFORM team

About

EZCONTACTFORM makes it easy to capture customer information and react. This project is the source code of our embeddable widget. Configuration is driven by www.ezcontactform.com.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors