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">×</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:
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
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
Always use HTTPS in production
Validate origins by setting
ALLOWED_ORIGINS
Implement CSP headers on your site
Monitor form submissions for unusual activity
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); });
CORS/Origin Issues
// Problem: Origin not allowed // Solution: Check ALLOWED_ORIGINS configuration // Development: Should work automatically // Production: Add your domain to ALLOWED_ORIGINS
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; }
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 formcontainer
(HTMLElement): The DOM element to render the form inoptions
(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 readyformSubmission
: Form was submitted (success/error)formValidation
: Form validation state changedformError
: 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
Form not appearing: Check console for errors, verify container exists
Styling issues: Check for CSS conflicts, use specific selectors
Submission errors: Verify form UUID and company name are correct
CORS errors: Check ALLOWED_ORIGINS configuration
Support
For additional support email us at engineering@journeybee.io