Form Abandonment Tracker
Conversion Rate Optimization
Comprehensive form interaction tracking that monitors field-level engagement, identifies abandonment patterns, and provides detailed analytics for conversion rate optimization and lead generation improvement.
Key Features
Script Overview
What It Does
This script monitors form interactions in real-time, tracking when users start filling forms, which fields they complete, and when they abandon the process. Essential for identifying friction points in your conversion funnel.
Use Case
Essential for identifying friction points in lead generation forms and checkout processes. Perfect for optimizing conversion rates and understanding where users drop off in your forms.
Key Benefits
- •Identify form abandonment patterns
- •Track field-level completion rates
- •Optimize form conversion rates
- •Understand user behavior friction points
- •Improve lead generation performance
- •Data-driven form optimization insights
JavaScript Code
<script>
// Form Abandonment Tracker Script
// Version: 1.0
// Last Updated: 2025-09-09
(function() {
'use strict';
var config = {
// Time before considering form abandoned (milliseconds)
abandonmentTime: 30000, // 30 seconds
// Minimum interaction time before tracking (milliseconds)
minInteractionTime: 2000, // 2 seconds
// Form selectors to track
formSelectors: 'form',
// Field types to track
trackedFields: ['input', 'textarea', 'select'],
// Fields to exclude from tracking
excludeFields: ['[type="hidden"]', '[type="submit"]', '[type="button"]'],
// Events configuration
events: {
formStart: 'form_interaction_start',
formAbandonment: 'form_abandoned',
formSubmit: 'form_submitted',
fieldInteraction: 'form_field_interaction'
},
debug: false
};
var trackedForms = {};
var formTimers = {};
function debugLog(message, data) {
if (config.debug) {
console.log('[Form Tracker] ' + message, data || '');
}
}
function getFormIdentifier(form) {
return form.id || form.name || form.className || 'form-' + Array.prototype.indexOf.call(document.forms, form);
}
function getFieldInfo(field) {
return {
name: field.name || field.id || 'unnamed',
type: field.type || field.tagName.toLowerCase(),
placeholder: field.placeholder || '',
required: field.required || false
};
}
function initFormTracking(form) {
var formId = getFormIdentifier(form);
if (trackedForms[formId]) return; // Already tracking
trackedForms[formId] = {
form: form,
startTime: null,
fields: {},
interactions: 0,
isAbandoned: false,
isSubmitted: false
};
var fields = form.querySelectorAll(config.trackedFields.join(','));
var excludeSelector = config.excludeFields.join(',');
Array.prototype.forEach.call(fields, function(field) {
if (excludeSelector && field.matches(excludeSelector)) return;
var fieldInfo = getFieldInfo(field);
trackedForms[formId].fields[fieldInfo.name] = {
info: fieldInfo,
interacted: false,
completed: false,
focusTime: null,
totalFocusTime: 0
};
// Add event listeners
field.addEventListener('focus', function() {
handleFieldFocus(formId, fieldInfo.name);
});
field.addEventListener('blur', function() {
handleFieldBlur(formId, fieldInfo.name, field);
});
field.addEventListener('input', function() {
handleFieldInput(formId, fieldInfo.name, field);
});
});
// Form submit tracking
form.addEventListener('submit', function() {
handleFormSubmit(formId);
});
debugLog('Initialized tracking for form:', formId);
}
function handleFieldFocus(formId, fieldName) {
var formData = trackedForms[formId];
if (!formData) return;
var fieldData = formData.fields[fieldName];
if (!fieldData) return;
fieldData.focusTime = Date.now();
// Track first interaction with form
if (!formData.startTime) {
formData.startTime = Date.now();
trackFormEvent(config.events.formStart, formId, {
fields_count: Object.keys(formData.fields).length
});
// Start abandonment timer
startAbandonmentTimer(formId);
}
formData.interactions++;
fieldData.interacted = true;
debugLog('Field focused:', { form: formId, field: fieldName });
}
function handleFieldBlur(formId, fieldName, field) {
var formData = trackedForms[formId];
if (!formData) return;
var fieldData = formData.fields[fieldName];
if (!fieldData || !fieldData.focusTime) return;
var focusTime = Date.now() - fieldData.focusTime;
fieldData.totalFocusTime += focusTime;
fieldData.focusTime = null;
// Check if field is completed
var isCompleted = field.value && field.value.trim().length > 0;
if (isCompleted && !fieldData.completed) {
fieldData.completed = true;
trackFormEvent(config.events.fieldInteraction, formId, {
field_name: fieldName,
field_type: fieldData.info.type,
focus_time: focusTime,
total_focus_time: fieldData.totalFocusTime,
completed: true
});
}
debugLog('Field blurred:', {
form: formId,
field: fieldName,
focusTime: focusTime,
completed: isCompleted
});
}
function handleFieldInput(formId, fieldName, field) {
var formData = trackedForms[formId];
if (!formData) return;
// Reset abandonment timer on input
resetAbandonmentTimer(formId);
}
function handleFormSubmit(formId) {
var formData = trackedForms[formId];
if (!formData) return;
formData.isSubmitted = true;
clearAbandonmentTimer(formId);
var completedFields = 0;
var totalFocusTime = 0;
Object.keys(formData.fields).forEach(function(fieldName) {
var fieldData = formData.fields[fieldName];
if (fieldData.completed) completedFields++;
totalFocusTime += fieldData.totalFocusTime;
});
var completionTime = Date.now() - formData.startTime;
trackFormEvent(config.events.formSubmit, formId, {
completion_time: completionTime,
total_focus_time: totalFocusTime,
completed_fields: completedFields,
total_fields: Object.keys(formData.fields).length,
completion_rate: Math.round((completedFields / Object.keys(formData.fields).length) * 100)
});
debugLog('Form submitted:', {
form: formId,
completionTime: completionTime,
completedFields: completedFields
});
}
function startAbandonmentTimer(formId) {
clearAbandonmentTimer(formId); // Clear existing timer
formTimers[formId] = setTimeout(function() {
handleFormAbandonment(formId);
}, config.abandonmentTime);
}
function resetAbandonmentTimer(formId) {
startAbandonmentTimer(formId); // Restart timer
}
function clearAbandonmentTimer(formId) {
if (formTimers[formId]) {
clearTimeout(formTimers[formId]);
delete formTimers[formId];
}
}
function handleFormAbandonment(formId) {
var formData = trackedForms[formId];
if (!formData || formData.isSubmitted || formData.isAbandoned) return;
formData.isAbandoned = true;
var completedFields = 0;
var interactedFields = 0;
var totalFocusTime = 0;
Object.keys(formData.fields).forEach(function(fieldName) {
var fieldData = formData.fields[fieldName];
if (fieldData.completed) completedFields++;
if (fieldData.interacted) interactedFields++;
totalFocusTime += fieldData.totalFocusTime;
});
var abandonmentTime = Date.now() - formData.startTime;
trackFormEvent(config.events.formAbandonment, formId, {
abandonment_time: abandonmentTime,
total_focus_time: totalFocusTime,
completed_fields: completedFields,
interacted_fields: interactedFields,
total_fields: Object.keys(formData.fields).length,
completion_rate: Math.round((completedFields / Object.keys(formData.fields).length) * 100),
interaction_rate: Math.round((interactedFields / Object.keys(formData.fields).length) * 100)
});
debugLog('Form abandoned:', {
form: formId,
abandonmentTime: abandonmentTime,
completedFields: completedFields
});
}
function trackFormEvent(eventName, formId, additionalData) {
window.dataLayer = window.dataLayer || [];
var eventData = {
event: eventName,
form_id: formId,
form_url: window.location.href
};
if (additionalData) {
Object.keys(additionalData).forEach(function(key) {
eventData['form_' + key] = additionalData[key];
});
}
window.dataLayer.push(eventData);
debugLog('Form event tracked:', eventData);
}
function initAllForms() {
var forms = document.querySelectorAll(config.formSelectors);
Array.prototype.forEach.call(forms, function(form) {
initFormTracking(form);
});
debugLog('Initialized tracking for ' + forms.length + ' forms');
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAllForms);
} else {
initAllForms();
}
// Handle dynamically added forms
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
Array.prototype.forEach.call(mutation.addedNodes, function(node) {
if (node.nodeType === 1) {
if (node.tagName === 'FORM') {
initFormTracking(node);
} else if (node.querySelector) {
var forms = node.querySelectorAll(config.formSelectors);
Array.prototype.forEach.call(forms, initFormTracking);
}
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
// Expose utility functions
window.formAbandonmentTracker = {
getTrackedForms: function() {
return trackedForms;
},
reinitialize: function() {
initAllForms();
}
};
})();
</script>
💡 Pro Tip: Copy the entire code block above and paste it directly into a GTM Custom HTML tag. The script is self-contained and ready to use immediately.