Skip to content
Journeybee Help Center home
Journeybee Help Center home

Embeddable Forms

Overview

JourneyBee Forms provides a secure, customisable, and easy-to-embed form solution for collecting leads and data from your website or application. This guide covers everything you need to know about embedding JourneyBee forms, from basic implementation to advanced security and customisation features.

Quick Start

Basic Embedding

Embed a form in just 3 simple steps:

<!-- 1. Add the JourneyBee SDK script --> <script src="https://forms.journeybee.io/api/embed/sdk.js" async></script> <!-- 2. Create a container for your form --> <div id="my-form-container"></div> <!-- 3. Initialize the form --> <script> // Wait for the script to load window.addEventListener('load', function() { window.journeybee('init', 'your-form-uuid', document.getElementById('my-form-container'), { companyName: 'your-company-name', // Optional: defaults to 'journeybee' debug: false // Optional: enable for development }); }); </script>


React/Next.js Integration

For React applications, use this component pattern:

import { useEffect, useRef } from 'react'; function JourneyBeeForm({ formUuid, companyName, options = {} }) { const containerRef = useRef(null); const formRef = useRef(null); useEffect(() => { // Load the SDK script const script = document.createElement('script'); script.src = 'https://forms.journeybee.io/api/embed/sdk.js'; script.async = true; script.onload = () => { if (containerRef.current && window.journeybee) { formRef.current = window.journeybee('init', formUuid, containerRef.current, { companyName, debug: process.env.NODE_ENV === 'development', ...options }); } }; document.head.appendChild(script); // Cleanup return () => { if (formRef.current) { window.journeybee('destroy', formUuid); } document.head.removeChild(script); }; }, [formUuid, companyName, options]); return <div ref={containerRef} style={{ width: '100%', minHeight: '400px' }} />; } export default JourneyBeeForm;

Vue.js Integration

<template> <div ref="formContainer" class="journeybee-form"></div> </template> <script> export default { name: 'JourneyBeeForm', props: { formUuid: { type: String, required: true }, companyName: { type: String, default: 'journeybee' } }, mounted() { this.loadForm(); }, beforeUnmount() { if (this.form) { window.journeybee('destroy', this.formUuid); } }, methods: { loadForm() { const script = document.createElement('script'); script.src = 'https://forms.journeybee.io/api/embed/sdk.js'; script.async = true; script.onload = () => { this.form = window.journeybee('init', this.formUuid, this.$refs.formContainer, { companyName: this.companyName, debug: process.env.NODE_ENV === 'development' }); }; document.head.appendChild(script); } } }; </script> <style scoped> .journeybee-form { width: 100%; min-height: 400px; } </style>

Advanced Configuration

Form Options

The journeybee('init') method accepts various options for customisation:

window.journeybee('init', formUuid, container, { // Basic Options companyName: 'your-company-name', debug: false, // Theming customization: { theme: { colors: { primary: '#3b82f6', secondary: '#f1f5f9', background: '#ffffff', text: '#1e293b', border: '#cbd5e1', error: '#ef4444' }, typography: { fontFamily: 'Inter, sans-serif', fontSize: { base: '16px', label: '14px', input: '16px' } }, spacing: { padding: '24px', gap: '16px' }, borders: { radius: '8px', width: '1px', style: 'solid' } } }, // Pre-fill form data prefill: { first_name: 'John', last_name: 'Doe', email: 'john@example.com', company_name: 'Acme Corp' }, // Event Callbacks onReady: function() { console.log('Form is ready'); }, onSuccess: function(data) { console.log('Form submitted successfully:', data); // Hide modal, show success message, redirect, etc. }, onError: function(error) { console.error('Form submission failed:', error); // Show error message, retry logic, etc. }, onValidation: function(validation) { console.log('Form validation:', validation); } });

Theme Presets

Use predefined themes for quick styling

// Dark theme customization: { theme: { colors: { primary: '#ffffff', secondary: '#1f2937', background: '#111827', text: '#ffffff', border: '#374151', error: '#ef4444' } } } // Blue theme customization: { theme: { colors: { primary: '#3b82f6', secondary: '#eff6ff', background: '#ffffff', text: '#1e293b', border: '#cbd5e1' } } } // Minimal theme customization: { theme: { colors: { primary: '#000000', background: '#ffffff', text: '#000000', border: '#e5e5e5' }, borders: { radius: '0px' // Square corners } } }

Field Customisation

Customize individual form fields:

customization: { fields: { first_name: { label: { text: 'Your First Name', required: true }, placeholder: 'Enter your first name', defaultValue: 'John' }, email: { validation: { required: true, pattern: '^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$', customMessage: 'Please enter a valid email address' } }, company_name: { label: { hide: true // Hide the label }, placeholder: 'Company Name (Optional)' } }, layout: { title: 'Get in Touch', description: 'Fill out this form and we\'ll get back to you within 24 hours.', submitButton: { text: 'Send Message', position: 'center' }, showBranding: false // Hide JourneyBee branding } }

Modal/Popup Implementation

Basic Implementation

<!DOCTYPE html> <html> <head> <title>JourneyBee Form Modal</title> <style> .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); } .modal-content { position: relative; background-color: white; margin: 5% auto; padding: 0; width: 90%; max-width: 600px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); max-height: 90vh; overflow-y: auto; } .close { position: absolute; right: 15px; top: 15px; font-size: 28px; font-weight: bold; cursor: pointer; z-index: 1001; } #form-container { width: 100%; min-height: 400px; } </style> </head> <body> <!-- Trigger button --> <button id="open-form">Open Contact Form</button> <!-- Modal --> <div id="form-modal" class="modal"> <div class="modal-content"> <span class="close">&times;</span> <div id="form-container"></div> </div> </div> <script src="https://forms.journeybee.io/api/embed/sdk.js" async></script> <script> let form = null; const modal = document.getElementById('form-modal'); const openBtn = document.getElementById('open-form'); const closeBtn = document.querySelector('.close'); openBtn.onclick = function() { modal.style.display = 'block'; // Initialize form when modal opens (only once) if (!form) { form = window.journeybee('init', 'your-form-uuid', document.getElementById('form-container'), { companyName: 'your-company', onSuccess: function(data) { console.log('Form submitted:', data); modal.style.display = 'none'; alert('Thank you! We\'ll be in touch soon.'); }, onError: function(error) { console.error('Form error:', error); alert('Something went wrong. Please try again.'); } }); } }; closeBtn.onclick = function() { modal.style.display = 'none'; }; window.onclick = function(event) { if (event.target === modal) { modal.style.display = 'none'; } }; </script> </body> </html>

React Modal with Form

import React, { useState, useEffect, useRef } from 'react'; function ContactModal({ isOpen, onClose, formUuid, companyName }) { const containerRef = useRef(null); const formRef = useRef(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (isOpen && !formRef.current) { // Load SDK script const script = document.createElement('script'); script.src = 'https://forms.journeybee.io/api/embed/sdk.js'; script.async = true; script.onload = () => { if (containerRef.current && window.journeybee) { formRef.current = window.journeybee('init', formUuid, containerRef.current, { companyName, onReady: () => setIsLoading(false), onSuccess: (data) => { console.log('Form submitted:', data); onClose(); // Show success notification }, onError: (error) => { console.error('Form error:', error); // Show error notification } }); } }; document.head.appendChild(script); } return () => { if (formRef.current && !isOpen) { window.journeybee('destroy', formUuid); formRef.current = null; } }; }, [isOpen, formUuid, companyName, onClose]); if (!isOpen) return null; return ( <div className="modal-overlay" onClick={onClose}> <div className="modal-content" onClick={e => e.stopPropagation()}> <button className="modal-close" onClick={onClose}>×</button> {isLoading && <div className="loading">Loading form...</div>} <div ref={containerRef} style={{ width: '100%', minHeight: '400px', display: isLoading ? 'none' : 'block' }} /> </div> </div> ); } export default ContactModal;


Security Considerations

Content Security Policy (CSP)

JourneyBee forms are designed to work with strict CSP policies. Add these directives to your CSP header:

Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com https://forms.journeybee.io; frame-src 'self' https://forms.journeybee.io; connect-src 'self' https://forms.journeybee.io; style-src 'self' 'unsafe-inline';


Origin Validation

JourneyBee automatically validates the origin of embedded forms. To configure allowed origins:

  1. Production: Set the ALLOWED_ORIGINS environment variable on your JourneyBee forms instance:

    ALLOWED_ORIGINS=https://yoursite.com,https://www.yoursite.com,https://app.yoursite.com
  2. Development: Local origins (localhost, 127.0.0.1) are automatically allowed in development mode.


Data Privacy

  • No Sensitive Data Storage: JourneyBee forms don’t store sensitive data like passwords or payment information

  • GDPR Compliant: All data collection respects GDPR requirements

  • Data Encryption: All data transmission uses HTTPS/TLS encryption

  • Cross-Origin Security: Strict origin validation prevents unauthorised embedding


Best Practices

  1. Always use HTTPS in production

  2. Validate origins by setting ALLOWED_ORIGINS

  3. Implement CSP headers on your site

  4. Monitor form submissions for unusual activity

  5. Regular security updates - keep the SDK up to date

Error Handling

Common Issues and Solutions

1. Form Not Loading

// Problem: Form container not found // Solution: Ensure container exists before initialization window.addEventListener('load', function() { const container = document.getElementById('form-container'); if (!container) { console.error('Form container not found'); return; } window.journeybee('init', formUuid, container, options); });
  1. CORS/Origin Issues

// Problem: Origin not allowed // Solution: Check ALLOWED_ORIGINS configuration // Development: Should work automatically // Production: Add your domain to ALLOWED_ORIGINS
  1. Styling Conflicts

    /* Problem: Parent styles affecting form */ /* Solution: Create isolated container */ .form-container { all: initial; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; }
  2. Form not resizing

// Problem: Form height not adjusting // Solution: Ensure parent container allows height changes .form-container { height: auto !important; min-height: 400px; overflow: visible; }

Error Event Handling

window.journeybee('init', formUuid, container, { onError: function(error) { switch(error.code) { case 'NETWORK_ERROR': showNotification('Network error. Please check your connection.', 'error'); break; case 'VALIDATION_ERROR': showNotification('Please check your form inputs.', 'warning'); break; case 'DUPLICATE_LEAD': showNotification('This information has already been submitted.', 'info'); break; default: showNotification('Something went wrong. Please try again.', 'error'); } } });

Performance Optimization

Lazy Loading

Load forms only when needed to improve page performance:

// Intersection Observer for lazy loading const formObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { loadJourneyBeeForm(entry.target); formObserver.unobserve(entry.target); } }); }); // Observe form containers document.querySelectorAll('.lazy-form').forEach(container => { formObserver.observe(container); }); function loadJourneyBeeForm(container) { const script = document.createElement('script'); script.src = 'https://forms.journeybee.io/api/embed/sdk.js'; script.async = true; script.onload = () => { window.journeybee('init', container.dataset.formUuid, container, { companyName: container.dataset.companyName }); }; document.head.appendChild(script); }

Preloading

For critical forms, preload the SDK:

<link rel="preload" href="https://forms.journeybee.io/api/embed/sdk.js" as="script">

Analytics and Tracking

Google Analytics Integration

window.journeybee('init', formUuid, container, { onReady: function() { // Track form view gtag('event', 'form_view', { event_category: 'engagement', event_label: 'contact_form' }); }, onSuccess: function(data) { // Track successful submission gtag('event', 'form_submit', { event_category: 'conversion', event_label: 'contact_form', value: 1 }); }, onError: function(error) { // Track errors gtag('event', 'form_error', { event_category: 'error', event_label: error.code || 'unknown' }); } });

Custom Event Tracking

// Listen for form events window.addEventListener('message', function(event) { if (event.origin !== 'https://forms.journeybee.io') return; const { type, formUuid } = event.data; switch(type) { case 'formSubmission': // Custom tracking logic trackFormSubmission(formUuid, event.data); break; case 'formError': // Error tracking trackFormError(formUuid, event.data); break; } });


API Reference

Methods

journeybee('init', formUuid, container, options)

Initialise a new form instance.

Parameters:

  • formUuid (string): The unique identifier for your form

  • container (HTMLElement): The DOM element to render the form in

  • options (object): Configuration options

Returns:

Form instance object

journeybee('destroy', formUuid)Destroy a form instance and clean up resources.Parameters:

  • formUuid (string): The form UUID to destroy

Events

Forms emit various events that you can listen to:

// Listen to all form messages window.addEventListener('message', function(event) { if (event.origin !== 'https://forms.journeybee.io') return; console.log('Form event:', event.data); });

Event Types:

  • ready: Form is loaded and ready

  • formSubmission: Form was submitted (success/error)

  • formValidation: Form validation state changed

  • formError: An error occurred

Configuration Schema

The complete configuration schema with validation:

interface FormOptions { companyName?: string; debug?: boolean; customization?: { theme?: { colors?: { primary?: string; // Hex, rgb, or rgba secondary?: string; background?: string; text?: string; border?: string; error?: string; success?: string; }; typography?: { fontFamily?: string; fontSize?: { base?: string; // e.g., '16px', '1rem' label?: string; input?: string; }; fontWeight?: 'normal' | 'medium' | 'semibold' | 'bold'; }; spacing?: { padding?: string; // e.g., '24px', '1.5rem' gap?: string; }; borders?: { width?: string; radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' | string; style?: 'solid' | 'dashed' | 'dotted'; }; }; fields?: { [fieldName: string]: { label?: { text?: string; required?: boolean; hide?: boolean; }; placeholder?: string; defaultValue?: string | number | boolean; validation?: { required?: boolean; pattern?: string; minLength?: number; maxLength?: number; min?: number; max?: number; customMessage?: string; }; }; }; layout?: { title?: string; description?: string; submitButton?: { text?: string; position?: 'left' | 'center' | 'right'; }; showBranding?: boolean; }; }; prefill?: { first_name?: string; last_name?: string; email?: string; phone_number?: string; company_name?: string; [key: string]: any; }; onReady?: () => void; onSuccess?: (data: any) => void; onError?: (error: any) => void; onValidation?: (validation: any) => void; }

Common Issues

  1. Form not appearing: Check console for errors, verify container exists

  2. Styling issues: Check for CSS conflicts, use specific selectors

  3. Submission errors: Verify form UUID and company name are correct

  4. CORS errors: Check ALLOWED_ORIGINS configuration

Support

For additional support email us at engineering@journeybee.io