Form tracking
Snowplow form tracking creates three event types: change_form
, submit_form
and focus_form
. Using the enableFormTracking
method adds event listeners to all form elements and to all interactive elements inside forms (that is, all input
, textarea
, and select
elements).
This will only work for form elements which exist when it is called. If you are creating a form programmatically, call enableFormTracking
again after adding it to the document to track it. You can call enableFormTracking
multiple times without risk of duplicated events. From v3.2.0, if you are programmatically adding additional fields to a form after initially calling enableFormTracking
then calling it again after the new form fields are added will include them in form tracking.
Events on password fields will not be tracked.
Form events are automatically tracked once configured.
Installation
- JavaScript (tag)
- Browser (npm)
Tracker Distribution | Included |
---|---|
sp.js | ✅ |
sp.lite.js | ❌ |
Download:
Download from GitHub Releases (Recommended) | Github Releases (plugins.umd.zip) |
Available on jsDelivr | jsDelivr (latest) |
Available on unpkg | unpkg (latest) |
Note: The links to the CDNs above point to the current latest version. You should pin to a specific version when integrating this plugin on your website if you are using a third party CDN in production.
npm install @snowplow/browser-plugin-form-tracking@3
yarn add @snowplow/browser-plugin-form-tracking@3
pnpm add @snowplow/browser-plugin-form-tracking@3
Enable form tracking
- JavaScript (tag)
- Browser (npm)
window.snowplow('addPlugin',
"https://cdn.jsdelivr.net/npm/@snowplow/browser-plugin-form-tracking@3/dist/index.umd.min.js",
["snowplowFormTracking", "FormTrackingPlugin"]
);
snowplow('enableFormTracking');
This is part of the @snowplow/browser-plugin-form-tracking
plugin. You need to install it with your favorite package manager: npm install @snowplow/browser-plugin-form-tracking
and then initialize it:
import { newTracker, trackPageView } from '@snowplow/browser-tracker';
import { FormTrackingPlugin, enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
newTracker('sp1', '{{collector_url}}', {
appId: 'my-app-id',
plugins: [ FormTrackingPlugin() ],
});
enableFormTracking();
This will only work for form elements which exist when it is called. If you are creating a form programmatically, call enableFormTracking
again after adding it to the document to track it. You can call enableFormTracking
multiple times without risk of duplicated events. From v3.2.0, if you are programmatically adding additional fields to a form after initially calling enableFormTracking
then calling it again after the new form fields are added will include them in form tracking.
By default, all three event types are tracked. However, it is possible to subscribe only to specific event types using the options.events
option when enabling form tracking:
- JavaScript (tag)
- Browser (npm)
// subscribing to specific event types
snowplow('enableFormTracking', {
options: {
events: ['submit_form', 'focus_form', 'change_form']
},
});
// subscribing to specific event types
enableFormTracking({
options: {
events: ['submit_form', 'focus_form', 'change_form']
},
});
change_form
When a user changes the value of a textarea
, input
, or select
element inside a form, a change_form
event will be fired. It will capture the name, type, and new value of the element, and the id of the parent form.
submit_form
When a user submits a form, a submit_form
event will be fired. It will capture the id and classes of the form and the name, type, and value of all textarea
, input
, and select
elements inside the form.
Note that this will only work if the original form submission event is actually fired. If you prevent it from firing, for example by using a jQuery event handler which returns false
to handle clicks on the form's submission button, the Snowplow submit_form
event will not be fired.
focus_form
When a user focuses on a form element, a focus_form
event will be fired. It will capture the id and classes of the form and the name, type, and value of the textarea
, input
, or select
element inside the form that received focus.
Configuration
It may be that you do not want to track every field in a form, or every form on a page. You can customize form tracking by passing a configuration argument to the enableFormTracking
method. This argument should be an object with two elements named "forms" and "fields". The "forms" element determines which forms will be tracked; the "fields" element determines which fields inside the tracked forms will be tracked. As with link click tracking, there are three ways to configure each field: a denylist, an allowlist, or a filter function. You do not have to use the same method for both fields.
Denylists
This is an array of strings used to prevent certain elements from being tracked. Any form with a CSS class in the array will be ignored. Any field whose name property is in the array will be ignored. All other elements will be tracked.
Allowlists
This is an array of strings used to turn on tracking. Any form with a CSS class in the array will be tracked. Any field in a tracked form whose name property is in the array will be tracked. All other elements will be ignored.
Filter functions
This is a function used to determine which elements are tracked. The element is passed as the argument to the function and is tracked if and only if the value returned by the function is truthy.
Transform functions
This is a function used to transform data in each form field. The value and element (2.15.0+ only) are passed as arguments to the function and the tracked value is replaced by the value returned.
In versions prior to 3.16.0, the transform function would receive 2 arguments, that were different between submit_form
and change_form
or focus_form
events. More specifically, the transform function signature was:
type transformFn = (x: string | null, elt: ElementData | TrackedHTMLElement) => string | null;
For change_form
and focus_form
events the elt
argument was the tracked HTML element itself, while for submit_form
events, the elt
argument was an object of type ElementData
with only some of the original element's attributes:
interface ElementData extends Record<string, string | null | undefined> {
name: string;
value: string | null;
nodeName: string;
type?: string;
}
Since version 3.16.0, the transform function receives three arguments:
- The value of the element.
- Either the HTML element (for
change_form
andfocus_form
events) or an instance ofElementData
(forsubmit_form
events). - The HTML element (in all form tracking events).
The function signature is:
type transformFn = (
elementValue: string | null,
elementInfo: ElementData | TrackedHTMLElement,
elt: TrackedHTMLElement
) => string | null;
This means that you can now specify a transform function that applies the exact same logic to all submit_form
, change_form
and focus_form
events independent of the element's attributes the logic may depend on. For example:
- JavaScript (tag)
- Browser (npm)
function redactPII(eltValue, _, elt) {
if (elt.id === 'pid') {
return 'redacted';
}
return eltValue;
}
snowplow('enableFormTracking', {
options: {
fields: { transform: redactPII },
},
});
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
function redactPII(eltValue, _, elt) {
if (elt.id === 'pid') {
return 'redacted';
}
return eltValue;
}
enableFormTracking({
options: {
fields: { transform: redactPII },
},
});
It is recommended that the transform
function does not return a falsy value but a string even when the intention is to redact a value from being tracked.
E.g. Send "null"
over null
.
Examples
To track every form element and every field except those fields named "password":
- JavaScript (tag)
- Browser (npm)
var opts = {
forms: {
denylist: []
},
fields: {
denylist: ['password']
}
};
snowplow('enableFormTracking', { options: opts });
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
var options = {
forms: {
denylist: []
},
fields: {
denylist: ['password']
}
};
enableFormTracking({ options });
To track only the forms with CSS class "tracked", and only those fields whose ID is not "private":
- JavaScript (tag)
- Browser (npm)
var opts = {
forms: {
allowlist: ["tracked"]
},
fields: {
filter: function (elt) {
return elt.id !== "private";
}
}
};
snowplow('enableFormTracking', { options: opts });
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
var opts = {
forms: {
allowlist: ["tracked"]
},
fields: {
filter: function (elt) {
return elt.id !== "private";
}
}
};
enableFormTracking({ options: opts });
To transform the form fields with an MD5 hashing function:
- JavaScript (tag)
- Browser (npm)
// For JavaScript tracker versions before 3.16.0
// function hashMD5(value, elt) {
// // can use elt to make transformation decisions
// return MD5(value);
// }
// For JavaScript tracker versions 3.16.0+
function hashMD5(value, _, elt) {
// can use elt to make transformation decisions
return MD5(value);
}
var opts = {
forms: {
allowlist: ["tracked"]
},
fields: {
filter: function (elt) {
return elt.id !== "private";
},
transform: hashMD5
}
};
snowplow('enableFormTracking', { options: opts });
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
// For Browser tracker versions before 3.16.0
// function hashMD5(value, elt) {
// // can use elt to make transformation decisions
// return MD5(value);
// }
// For Browser tracker versions 3.16.0+
function hashMD5(value, _, elt) {
// can use elt to make transformation decisions
return MD5(value);
}
var options = {
forms: {
allowlist: ["tracked"]
},
fields: {
filter: function (elt) {
return elt.id !== "private";
},
transform: hashMD5
}
};
enableFormTracking({ options });
Tracking forms embedded inside iframes
The options for tracking forms inside of iframes are limited – browsers block access to contents of iframes that are from different domains than the parent page. We are not able to provide a solution to track events using trackers initialized on the parent page in such cases. However, since version 3.4, it is possible to track events from forms embedded in iframes loaded from the same domain as the parent page or iframes created using JavaScript on the parent page (e.g., HubSpot forms).
In case you are able to access form elements inside an iframe, you can pass them in the options.forms
argument when calling enableFormTracking
on the parent page. This will enable form tracking for the specific form elements. The feature may also be used for forms not embedded in iframes, but it's most useful in this particular case.
The following example shows how to identify the form elements inside an iframe and pass them to the enableFormTracking
function:
- JavaScript (tag)
- Browser (npm)
let iframe = document.getElementById('form_iframe'); // find the element for the iframe
let forms = iframe.contentWindow.document.getElementsByTagName('form'); // find form elements inside the iframe
snowplow('enableFormTracking', {
options: {
forms: forms // pass the embedded forms when enabling form tracking
},
});
let iframe = document.getElementById('form_iframe'); // find the element for the iframe
let forms = iframe.contentWindow.document.getElementsByTagName('form'); // find form elements inside the iframe
enableFormTracking({
options: {
forms: forms // pass the embedded forms when enabling form tracking
},
});
Custom context entities
Context entities can be sent with all form tracking events by supplying them in an array in the context
argument.
- JavaScript (tag)
- Browser (npm)
snowplow('enableFormTracking', { options: {}, context: [] });
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
enableFormTracking({ options: {}, context: [] });
These context entities can be dynamic, i.e. they can be traditional self-describing JSON objects, or callbacks that generate valid self-describing JSON objects.
For form change events, context generators are passed (elt, type, value)
, and form submission events are passed (elt, innerElements)
.
A dynamic context could therefore look something like this for form change events:
- JavaScript (tag)
- Browser (npm)
let dynamicContext = function (elt, type, value) {
// perform operations here to construct the context entity
return context;
};
snowplow('enableFormTracking', { options: {}, context: [dynamicContext] });
import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking';
var dynamicContext = function (elt, type, value) {
// perform operations here to construct the context entity
return context;
};
enableFormTracking({ options: {}, context: [dynamicContext] });