How to Use Genuine Forms

Complete guide to creating, customizing, and deploying forms to inbox in minutes

πŸš€ Quick Start

Get your first form running in under 2 minutes. Here's the fastest way to add a working contact form to any website:

Complete Example

Copy and paste this complete working example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contact Form</title>
</head>
<body>
    <!-- Load Genuine Forms -->
    <script type="module" 
      src="https://cryptng.github.io/genuine-forms-vanillajs/genuine-form.js">
    </script>

    <h1>Contact Us</h1>
    
    <genuine-form 
      name="contact-form"
      subject="New Contact Request" 
      api-key="YOUR_API_KEY">
      
      <label>Name:</label>
      <input name="name" required /><br/>
      
      <label>Email:</label>
      <input name="email" type="email" required /><br/>
      
      <label>Message:</label>
      <textarea name="message" rows="5" required></textarea><br/>
      
      <input name="terms" type="checkbox" required />
      <label>I agree to the terms</label><br/>
      
      <genuine-captcha>
        <button type="submit">Send Message</button>
      </genuine-captcha>
    </genuine-form>
</body>
</html>

That's it! Replace YOUR_API_KEY with your actual API key from the dashboard, and you have a fully working form with captcha protection.

πŸ“¦ Installation

Genuine Forms can be installed in two ways depending on your project setup:

Option A: CDN (Recommended for Quick Setup)

Simply add this script tag anywhere in your HTML file:

<script type="module" src="https://cryptng.github.io/genuine-forms-vanillajs/genuine-form.js"> </script>

Best for: Static sites, WordPress, simple projects, rapid prototyping

Option B: npm (For Build Tools)

If you're using a build system (Webpack, Vite, etc.), install via npm:

npm install @genuine-forms/web-components

Then import it in your app's entry point:

// app.js or main.js import '@genuine-forms/web-components';

Best for: React, Vue, Svelte, Angular, Next.js, Nuxt, or any framework with a build step

Verification

To verify the component is loaded, open your browser console and type:

console.log(customElements.get('genuine-form'));

If you see a function definition, you're all set!

πŸ“ Basic Usage

Anatomy of a Genuine Form

Every Genuine Form has three essential parts:

<genuine-form>
  <!-- 1. Your input fields -->
  <input name="email" type="email" required />
  
  <!-- 2. Captcha wrapper -->
  <genuine-captcha>
    <!-- 3. Submit button -->
    <button type="submit">Send</button>
  </genuine-captcha>
</genuine-form>

Supported Form Elements

Genuine Forms automatically collects data from these standard HTML elements:

Text Inputs

<input name="username" type="text" required /> <input name="email" type="email" required /> <input name="phone" type="tel" /> <input name="website" type="url" />

Textareas

<textarea name="message" rows="5" required></textarea>

Checkboxes

<input name="newsletter" type="checkbox" /> <input name="terms" type="checkbox" required />

Collected as: true or false

Radio Buttons

<input name="plan" type="radio" value="basic" /> <input name="plan" type="radio" value="pro" /> <input name="plan" type="radio" value="enterprise" required />

Collected as: the selected value (e.g., "pro")

Select Dropdowns

<!-- Single select --> <select name="country" required> <option value="">Select country</option> <option value="us">United States</option> <option value="uk">United Kingdom</option> </select> <!-- Multi-select --> <select name="interests" multiple> <option value="design">Design</option> <option value="dev">Development</option> <option value="marketing">Marketing</option> </select>

Important: All form elements must have a name attribute to be collected. Elements without names are ignored.

HTML5 Validation

Genuine Forms respects standard HTML5 validation attributes:

  • required β€” Field must have a value
  • type="email" β€” Validates email format
  • type="url" β€” Validates URL format
  • minlength / maxlength β€” Character limits
  • pattern β€” Custom regex validation
  • min / max β€” Number ranges
<input name="age" type="number" min="18" max="120" required /> <input name="zipcode" pattern="[0-9]{5}" required />

βš™οΈ Component Attributes

Configure your form's behavior with these attributes on the <genuine-form> element:

Attribute Type Required Description
api-key string Yes* Your Genuine Forms API key from the dashboard
subject string No Email subject line (default: "Generic Subject")
name string No** Unique form identifier (default: "genuine-form")
api-url string No Custom API endpoint (advanced usage)
receiver string No XOR-encrypted email for Developer Preview mode

* Required in production. Use receiver for Developer Preview.
** Required when using multiple forms on the same page.

Usage Examples

Production Setup

<genuine-form 
  api-key="gf_1234567890abcdef"
  subject="Contact Form Submission"
  name="contact-form">
  <!-- form fields -->
</genuine-form>

Developer Preview (No API Key)

<genuine-form 
  receiver="16,65,20,7,21,12,28,24,0,35,21,10,27,20..."
  subject="Test Submission"
  name="test-form">
  <!-- form fields -->
</genuine-form>

Getting your API key: Log into your Genuine Forms dashboard and navigate to Settings β†’ API Keys. Your key starts with gf_

Custom Backend

If you're using your own backend instead of Genuine Forms' hosted service:

<genuine-form 
  api-url="https://your-api.com/send-email"
  api-key="your-custom-key"
  subject="Form Submission">
  <!-- form fields -->
</genuine-form>

Custom Backend Requirements: Your endpoint must accept GET requests with query parameters: captchaSolution, captchaSecret, apiKey, subject, and body. It should return JSON with a body property on success.

πŸ”” Event System

Genuine Forms provides a powerful event system to respond to form actions. Access events through the global window.genuineForms object.

Available Events

Event Parameters When Triggered
send-response { ok, body?, error? } After submission completes (success or failure)
validation-failed none When form validation fails on submit
started-sending none Immediately before sending the request
finished-sending none After request completes (regardless of result)

Basic Event Usage

<script>
// Wait for the form to be ready
window.addEventListener('DOMContentLoaded', () => {
  
  // Register event handlers
  window.genuineForms['contact-form'].on('send-response', (response) => {
    if (response.ok) {
      alert('Thank you! Your message was sent successfully.');
      console.log('Server response:', response.body);
    } else {
      alert('Sorry, there was an error sending your message.');
      console.error('Error:', response.error);
    }
  });

  window.genuineForms['contact-form'].on('validation-failed', () => {
    alert('Please fill out all required fields correctly.');
  });

});
</script>

Complete Example: Show Loading State

<genuine-form name="contact-form" api-key="YOUR_KEY">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  
  <genuine-captcha>
    <button id="submit-btn" type="submit">Send Message</button>
  </genuine-captcha>
  
  <div id="status"></div>
</genuine-form>

<script>
const submitBtn = document.getElementById('submit-btn');
const status = document.getElementById('status');

window.genuineForms['contact-form'].on('started-sending', () => {
  submitBtn.disabled = true;
  submitBtn.textContent = 'Sending...';
  status.textContent = 'Submitting your message...';
  status.style.color = '#667eea';
});

window.genuineForms['contact-form'].on('send-response', (response) => {
  submitBtn.disabled = false;
  submitBtn.textContent = 'Send Message';
  
  if (response.ok) {
    status.textContent = 'βœ“ Message sent successfully!';
    status.style.color = '#10b981';
    
    // Clear the form
    document.querySelector('genuine-form').reset();
  } else {
    status.textContent = 'βœ— Failed to send message. Please try again.';
    status.style.color = '#dc2626';
  }
});

window.genuineForms['contact-form'].on('validation-failed', () => {
  status.textContent = '⚠ Please fill out all required fields.';
  status.style.color = '#f59e0b';
});
</script>

Removing Event Handlers

// Remove a specific event handler
window.genuineForms['contact-form'].off('send-response');

// Remove all handlers for an event
window.genuineForms['contact-form'].off('validation-failed');

Tip: In single-page applications, remove event handlers when components unmount to prevent memory leaks.

🎨 Advanced Customization

Override default behavior by defining global functions before your form initializes. The component waits up to 15 seconds for these functions.

Custom Validation Logic

Replace the built-in validation with your own rules:

<script>
window.genuineFormHandleValidate = (formName, formElement) => {
  // Get form fields
  const email = formElement.querySelector('[name="email"]');
  const age = formElement.querySelector('[name="age"]');
  const terms = formElement.querySelector('[name="terms"]');
  
  // Custom validation rules
  if (!email.value.endsWith('@company.com')) {
    alert('Please use your company email address');
    return false;
  }
  
  if (parseInt(age.value) < 18) {
    alert('You must be 18 or older');
    return false;
  }
  
  if (!terms.checked) {
    alert('You must accept the terms and conditions');
    return false;
  }
  
  return true; // All checks passed
};
</script>

Custom Subject & Body Generation

Control exactly what gets sent in the email:

<script>
window.genuineFormGenerateSubjectAndBody = (formName, formElement, defaultSubject) => {
  // Collect form data
  const inputs = formElement.querySelectorAll('[name]');
  const data = {};
  
  inputs.forEach(input => {
    if (input.type === 'checkbox') {
      data[input.name] = input.checked;
    } else if (input.type === 'radio') {
      if (input.checked) data[input.name] = input.value;
    } else if (input.tagName === 'SELECT' && input.multiple) {
      data[input.name] = Array.from(input.selectedOptions).map(opt => opt.value);
    } else {
      data[input.name] = input.value;
    }
  });
  
  // Create custom subject with user's name
  const customSubject = `${defaultSubject} - ${data.name}`;
  
  // Create formatted body
  const customBody = `
New Form Submission
==================
Name: ${data.name}
Email: ${data.email}
Phone: ${data.phone || 'Not provided'}
Message:
${data.message}

Submitted: ${new Date().toLocaleString()}
Form: ${formName}
  `.trim();
  
  return {
    subject: customSubject,
    body: customBody
  };
};
</script>

Initialization Callback

Run code when the form is ready:

<script>
window.genuineFormHandleInitialized = (formName, formElement) => {
  console.log(`Form "${formName}" is now ready!`);
  
  // Pre-fill form fields from URL parameters
  const urlParams = new URLSearchParams(window.location.search);
  const email = formElement.querySelector('[name="email"]');
  if (email && urlParams.has('email')) {
    email.value = urlParams.get('email');
  }
  
  // Add custom analytics tracking
  formElement.addEventListener('submit', () => {
    // Your analytics code here
    console.log('Form submission tracked');
  });
};
</script>

Important: Define these global functions before the <genuine-form> element appears in your HTML, or place them in a separate script tag in the <head>.

Complete Custom Example

<!DOCTYPE html>
<html>
<head>
  <script type="module" 
    src="https://cryptng.github.io/genuine-forms-vanillajs/genuine-form.js">
  </script>
  
  <script>
    // Custom validation
    window.genuineFormHandleValidate = (formName, formElement) => {
      const email = formElement.querySelector('[name="email"]').value;
      return email.includes('@'); // Simple check
    };
    
    // Custom email format
    window.genuineFormGenerateSubjectAndBody = (name, el, subject) => {
      const formData = {};
      el.querySelectorAll('[name]').forEach(input => {
        formData[input.name] = input.value;
      });
      
      return {
        subject: `[${name}] ${subject}`,
        body: JSON.stringify(formData, null, 2)
      };
    };
    
    // Initialization
    window.genuineFormHandleInitialized = (formName, formElement) => {
      console.log('Form ready:', formName);
    };
  </script>
</head>
<body>
  <genuine-form name="custom-form" api-key="YOUR_KEY">
    <!-- form fields -->
  </genuine-form>
</body>
</html>

🎨 Styling Your Form

Genuine Forms uses Shadow DOM with CSS custom properties, giving you full styling control while maintaining encapsulation.

CSS Custom Properties

Override these variables to customize the form container:

<style>
genuine-form {
  /* Layout */
  --form-display: flex;
  --form-flex-direction: column;
  --form-gap: 1rem;
  --form-padding: 1rem;
  
  /* Appearance */
  --form-border-radius: 0.5rem;
  --form-border: 1px solid #ccc;
  --form-background-color: #fff;
  --form-box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>

Styling Slotted Content

Your form fields are slotted content, so style them normally:

<style>
/* Style inputs inside genuine-form */
genuine-form input,
genuine-form textarea,
genuine-form select {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid #e5e7eb;
  border-radius: 0.5rem;
  font-size: 1rem;
  transition: border-color 0.2s;
}

genuine-form input:focus,
genuine-form textarea:focus,
genuine-form select:focus {
  outline: none;
  border-color: #667eea;
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

/* Style labels */
genuine-form label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 600;
  color: #374151;
}

/* Style submit button */
genuine-form button[type="submit"] {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 0.875rem 2rem;
  border: none;
  border-radius: 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
}

genuine-form button[type="submit"]:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}

genuine-form button[type="submit"]:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}
</style>

Complete Styled Example

<!DOCTYPE html>
<html>
<head>
  <script type="module" 
    src="https://cryptng.github.io/genuine-forms-vanillajs/genuine-form.js">
  </script>
  
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      max-width: 600px;
      margin: 2rem auto;
      padding: 0 1rem;
    }
    
    genuine-form {
      --form-gap: 1.5rem;
      --form-padding: 2rem;
      --form-border-radius: 1rem;
      --form-border: none;
      --form-background-color: #f9fafb;
      --form-box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    }
    
    genuine-form label {
      display: block;
      margin-bottom: 0.5rem;
      font-weight: 600;
      color: #374151;
    }
    
    genuine-form input,
    genuine-form textarea {
      width: 100%;
      padding: 0.875rem;
      border: 2px solid #e5e7eb;
      border-radius: 0.5rem;
      font-size: 1rem;
      font-family: inherit;
      transition: all 0.2s;
    }
    
    genuine-form input:focus,
    genuine-form textarea:focus {
      outline: none;
      border-color: #667eea;
      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }
    
    genuine-form input[type="checkbox"] {
      width: auto;
      margin-right: 0.5rem;
    }
    
    genuine-form button[type="submit"] {
      width: 100%;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 1rem;
      border: none;
      border-radius: 0.5rem;
      font-size: 1.125rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s;
    }
    
    genuine-form button[type="submit"]:hover {
      transform: translateY(-2px);
      box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
    }
  </style>
</head>
<body>
  <h1>Contact Us</h1>
  
  <genuine-form name="styled-form" api-key="YOUR_KEY" subject="Contact">
    <div>
      <label for="name">Name</label>
      <input id="name" name="name" required />
    </div>
    
    <div>
      <label for="email">Email</label>
      <input id="email" name="email" type="email" required />
    </div>
    
    <div>
      <label for="message">Message</label>
      <textarea id="message" name="message" rows="5" required></textarea>
    </div>
    
    <div>
      <input id="terms" name="terms" type="checkbox" required />
      <label for="terms" style="display: inline;">I agree to the terms</label>
    </div>
    
    <genuine-captcha>
      <button type="submit">Send Message</button>
    </genuine-captcha>
  </genuine-form>
</body>
</html>

Pro Tip: Use CSS Grid or Flexbox on wrapper divs inside the form for more complex layouts like multi-column forms.

πŸ“‹ Multiple Forms on One Page

When using multiple forms on the same page, give each form a unique name attribute:

<!-- Contact Form -->
<genuine-form 
  name="contact-form" 
  subject="Contact Request" 
  api-key="YOUR_KEY">
  
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  
  <genuine-captcha>
    <button type="submit">Contact Us</button>
  </genuine-captcha>
</genuine-form>

<!-- Newsletter Form -->
<genuine-form 
  name="newsletter-form" 
  subject="Newsletter Signup" 
  api-key="YOUR_KEY">
  
  <input name="email" type="email" required />
  <input name="name" required />
  
  <genuine-captcha>
    <button type="submit">Subscribe</button>
  </genuine-captcha>
</genuine-form>

Handling Events for Multiple Forms

<script>
// Contact form events
window.genuineForms['contact-form'].on('send-response', (response) => {
  if (response.ok) {
    document.getElementById('contact-status').textContent = 
      'Thank you for contacting us!';
  }
});

// Newsletter form events
window.genuineForms['newsletter-form'].on('send-response', (response) => {
  if (response.ok) {
    document.getElementById('newsletter-status').textContent = 
      'Successfully subscribed!';
  }
});
</script>

Custom Functions for Specific Forms

The custom hook functions receive the form name, so you can apply different logic:

<script>
window.genuineFormHandleValidate = (formName, formElement) => {
  if (formName === 'contact-form') {
    // Custom validation for contact form
    const message = formElement.querySelector('[name="message"]');
    if (message.value.length < 10) {
      alert('Message must be at least 10 characters');
      return false;
    }
  }
  
  if (formName === 'newsletter-form') {
    // Custom validation for newsletter
    const email = formElement.querySelector('[name="email"]');
    if (!email.value.includes('@')) {
      alert('Please enter a valid email');
      return false;
    }
  }
  
  return true;
};

window.genuineFormGenerateSubjectAndBody = (formName, formElement, subject) => {
  const inputs = formElement.querySelectorAll('[name]');
  const data = {};
  
  inputs.forEach(input => {
    data[input.name] = input.value;
  });
  
  // Different formatting for each form
  let body;
  if (formName === 'contact-form') {
    body = `Contact Request\n\nEmail: ${data.email}\nMessage: ${data.message}`;
  } else if (formName === 'newsletter-form') {
    body = `Newsletter Signup\n\nName: ${data.name}\nEmail: ${data.email}`;
  }
  
  return {
    subject: `[${formName}] ${subject}`,
    body: body
  };
};
</script>

Warning: If you have multiple forms without unique names, you'll see a console warning, and only the first submit button will work properly.

πŸ”§ Troubleshooting

Submit Button Does Nothing

Possible causes:

  • Submit button is not inside <genuine-captcha>
  • Captcha hasn't been verified yet
  • Form validation is failing
  • No API key or receiver is set
  • Required fields are empty

Solution:

// Check browser console for errors
// Verify structure:
<genuine-form api-key="YOUR_KEY">
  <input name="test" required />
  <genuine-captcha>
    <button type="submit">Send</button>  <!-- Must be here -->
  </genuine-captcha>
</genuine-form>

Captcha Doesn't Appear

Possible causes:

  • Genuine Captcha script is blocked
  • Ad blocker is interfering
  • CSP (Content Security Policy) is too restrictive

Solution:

<!-- Check that captcha script loads -->
<!-- It's automatically imported, but you can verify in Network tab -->

<!-- If using CSP, allow: -->
<meta http-equiv="Content-Security-Policy" 
  content="script-src 'self' https://cryptng.github.io; 
           style-src 'self' 'unsafe-inline';">

Form Data Not Collected

Possible causes:

  • Form elements missing name attribute
  • Elements are outside <genuine-form>

Solution:

<!-- ❌ Wrong - no name attribute -->
<input type="email" required />

<!-- βœ“ Correct - has name -->
<input name="email" type="email" required />

Events Not Firing

Possible causes:

  • Accessing events before form is initialized
  • Wrong form name
  • Script runs before DOM is ready

Solution:

<script>
// ❌ Wrong - might run too early
window.genuineForms['my-form'].on('send-response', handler);

// βœ“ Correct - wait for DOM
window.addEventListener('DOMContentLoaded', () => {
  window.genuineForms['my-form'].on('send-response', handler);
});

// Or check if it exists
if (window.genuineForms && window.genuineForms['my-form']) {
  window.genuineForms['my-form'].on('send-response', handler);
}
</script>

Custom Functions Not Working

Possible causes:

  • Functions defined after component loads
  • Typo in function name

Solution:

<!-- βœ“ Define before component -->
<head>
  <script>
    window.genuineFormHandleValidate = (name, el) => {
      // Your logic
      return true;
    };
  </script>
  
  <script type="module" src="genuine-form.js"></script>
</head>
<body>
  <genuine-form>...</genuine-form>
</body>

CORS Errors

Possible causes:

  • Custom api-url doesn't allow your domain
  • Testing from file:// protocol

Solution:

// If using custom backend, ensure CORS headers:
Access-Control-Allow-Origin: *
// Or specific domain:
Access-Control-Allow-Origin: https://yourdomain.com

// For local testing, use a local server:
// Python: python -m http.server 8000
// Node: npx serve
// PHP: php -S localhost:8000

Multiple Forms - Wrong Button Clicked

Possible causes:

  • Forms don't have unique name attributes

Solution:

<!-- βœ“ Each form needs unique name -->
<genuine-form name="form-1" api-key="KEY">...</genuine-form>
<genuine-form name="form-2" api-key="KEY">...</genuine-form>
Still Having Issues?

Debugging Checklist:

  1. Open browser console (F12) and check for errors
  2. Verify component loaded: console.log(customElements.get('genuine-form'))
  3. Check API key is correct and has credits
  4. Ensure all required fields have values
  5. Verify captcha is completed
  6. Test with a minimal example first

Need more help? Contact us at below with your browser console output, code snippet or any question you have.

Questions? Ask them all. πŸ™

Question Sent ✨

We've received your questions and will provide feedback soon. Thanks for your patienceπŸ™.

Ready to Add Forms to Your Site?

Start free with 1,000 credits. No credit card required.

Get Started Free