Hello,
After doing a lot of research about how to use GTM to track form submissions into GA as Goal Completions, I mainly followed this article: https://nettlesnet.com/marketo-form-tracking-google-tag-manager-analytics/
Whenever I preview and test, it works, but after having it live for over a week, the GA goal completions (and even GA events that came in) is drastically lower than the number of actual people who 'filled out form" when I pull it from Marketo as either a Smart List or Landing Page Conversion Report.
After some more research, stumbled on a lot of discussions from @SanfordWhiteman to include an eventCallback to the GTM tag, so I did - but upon preview in GTM, it keeps giving me the error "Parse error. '}' expected (screenshot of error attached).
So I have 2 questions:
1. Is this Custom HTML in the GTM tag correct? I have subsequent Trigger (that fires on All Pages) and Universal Analytics Tag to send this back to GA
2. If the above is correct, what's causing this error when I added in the eventCallback?
Thanks!
<script>
/**
* push Marketo form events and values to Google Tag Manager via the data layer
* uses the Marketo Forms 2.0 API
*/
(function marketoFormListener (MktoForms2) {
"use strict";
/**
* helper function to push invalid Marketo field errors to GTM
* @returns {undefined}
*/
function waitForError () {
var element, input, mktoErrorMsg;
// check for error message
element = document.querySelector(".mktoErrorMsg");
if (element) {
mktoErrorMsg = element.textContent || element.innerText;
// look for invalid input
input = document.querySelector("input.mktoInvalid, .mktoInvalid input");
window.dataLayer.push({
"event": "mkto.form.error",
"mkto.form.error.message": mktoErrorMsg,
"gtm.element": input,
"gtm.elementClasses": input && input.className || "",
"gtm.elementId": input && input.id || "",
"gtm.elementName": input && input.name || "",
"gtm.elementTarget": input && input.target || ""
});
}
}
if (MktoForms2) {
MktoForms2.whenReady(function handleReady (form) {
window.dataLayer.push({
"event": "mkto.form.ready",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
form.onValidate(function handleValidate (valid) {
window.dataLayer.push({
"event": "mkto.form.validate",
"mkto.form.valid": valid
});
// wait for the error message to appear
setTimeout(waitForError, 0);
});
form.onSubmit(function handleSubmit (thisForm) {
var button;
button = thisForm.getFormElem().find("button[type=\"submit\"]");
window.dataLayer.push({
"event": "mkto.form.submit",
"mkto.form.id": thisForm.getId(),
"mkto.form.submittable": thisForm.submittable(),
"mkto.form.allFieldsFilled": thisForm.allFieldsFilled(),
"mkto.form.values": thisForm.getValues(),
"mkto.form.button": {
"classes": button.attr("class"),
"text": button.text(),
"type": "submit"
}
});
});
form.onSuccess(function handleSuccess (values, followUpUrl) {
window.dataLayer.push({
"event": "mkto.form.success",
"mkto.form.values": values,
"mkto.form.followUpUrl": followUpUrl
"eventCallback": function () {
document.location.href = followUpUrl;
},
"eventTimeout" : 3000
});
return false;
});
});
MktoForms2.whenRendered(function handleRendered (form) {
window.dataLayer.push({
"event": "mkto.form.rendered",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
});
}
}(window.MktoForms2));
</script>
Solved! Go to Solution.
That setTimeout makes my stomach bubble a little bit... but anyway, you have a missing comma and misplaced parenthesis. Without the syntax errors:
(function marketoFormListener (MktoForms2) {
"use strict";
/**
* helper function to push invalid Marketo field errors to GTM
* @returns {undefined}
*/
function waitForError () {
var element, input, mktoErrorMsg;
// check for error message
element = document.querySelector(".mktoErrorMsg");
if (element) {
mktoErrorMsg = element.textContent || element.innerText;
// look for invalid input
input = document.querySelector("input.mktoInvalid, .mktoInvalid input");
window.dataLayer.push({
"event": "mkto.form.error",
"mkto.form.error.message": mktoErrorMsg,
"gtm.element": input,
"gtm.elementClasses": input && input.className || "",
"gtm.elementId": input && input.id || "",
"gtm.elementName": input && input.name || "",
"gtm.elementTarget": input && input.target || ""
});
}
}
if (MktoForms2) {
MktoForms2.whenReady(function handleReady (form) {
window.dataLayer.push({
"event": "mkto.form.ready",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
form.onValidate(function handleValidate (valid) {
window.dataLayer.push({
"event": "mkto.form.validate",
"mkto.form.valid": valid
});
// wait for the error message to appear
setTimeout(waitForError, 0);
});
form.onSubmit(function handleSubmit (thisForm) {
var button;
button = thisForm.getFormElem().find("button[type=\"submit\"]");
window.dataLayer.push({
"event": "mkto.form.submit",
"mkto.form.id": thisForm.getId(),
"mkto.form.submittable": thisForm.submittable(),
"mkto.form.allFieldsFilled": thisForm.allFieldsFilled(),
"mkto.form.values": thisForm.getValues(),
"mkto.form.button": {
"classes": button.attr("class"),
"text": button.text(),
"type": "submit"
}
});
});
form.onSuccess(function handleSuccess (values, followUpUrl) {
window.dataLayer.push({
"event": "mkto.form.success",
"mkto.form.values": values,
"mkto.form.followUpUrl": followUpUrl,
"eventCallback": function () {
document.location.href = followUpUrl;
},
"eventTimeout" : 3000
});
return false;
});
});
MktoForms2.whenRendered(function handleRendered (form) {
window.dataLayer.push({
"event": "mkto.form.rendered",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
});
}
})(window.MktoForms2);
That setTimeout makes my stomach bubble a little bit... but anyway, you have a missing comma and misplaced parenthesis. Without the syntax errors:
(function marketoFormListener (MktoForms2) {
"use strict";
/**
* helper function to push invalid Marketo field errors to GTM
* @returns {undefined}
*/
function waitForError () {
var element, input, mktoErrorMsg;
// check for error message
element = document.querySelector(".mktoErrorMsg");
if (element) {
mktoErrorMsg = element.textContent || element.innerText;
// look for invalid input
input = document.querySelector("input.mktoInvalid, .mktoInvalid input");
window.dataLayer.push({
"event": "mkto.form.error",
"mkto.form.error.message": mktoErrorMsg,
"gtm.element": input,
"gtm.elementClasses": input && input.className || "",
"gtm.elementId": input && input.id || "",
"gtm.elementName": input && input.name || "",
"gtm.elementTarget": input && input.target || ""
});
}
}
if (MktoForms2) {
MktoForms2.whenReady(function handleReady (form) {
window.dataLayer.push({
"event": "mkto.form.ready",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
form.onValidate(function handleValidate (valid) {
window.dataLayer.push({
"event": "mkto.form.validate",
"mkto.form.valid": valid
});
// wait for the error message to appear
setTimeout(waitForError, 0);
});
form.onSubmit(function handleSubmit (thisForm) {
var button;
button = thisForm.getFormElem().find("button[type=\"submit\"]");
window.dataLayer.push({
"event": "mkto.form.submit",
"mkto.form.id": thisForm.getId(),
"mkto.form.submittable": thisForm.submittable(),
"mkto.form.allFieldsFilled": thisForm.allFieldsFilled(),
"mkto.form.values": thisForm.getValues(),
"mkto.form.button": {
"classes": button.attr("class"),
"text": button.text(),
"type": "submit"
}
});
});
form.onSuccess(function handleSuccess (values, followUpUrl) {
window.dataLayer.push({
"event": "mkto.form.success",
"mkto.form.values": values,
"mkto.form.followUpUrl": followUpUrl,
"eventCallback": function () {
document.location.href = followUpUrl;
},
"eventTimeout" : 3000
});
return false;
});
});
MktoForms2.whenRendered(function handleRendered (form) {
window.dataLayer.push({
"event": "mkto.form.rendered",
"mkto.form.id": form.getId(),
"mkto.form.submittable": form.submittable(),
"mkto.form.allFieldsFilled": form.allFieldsFilled(),
"mkto.form.values": form.getValues()
});
});
}
})(window.MktoForms2);
Thanks so much @SanfordWhiteman! GTM preview/tested fine now without those errors and we'll now see if this modification will align the reports with Marketo's better.
What was the reason that the setTimeout isn't the best idea? I literally just took the script from the blog so not tied to it.
The reason we're going through GTM instead of sending an event directly to GA is because there are hundreds of Marketo LPs live, so to add the additional script on the LP itself to push the event directly to GA wouldn't be scalable for us.
Alright I just pulled some prelim report to see if the GA Goal Completions set for these form.success events are tracking closer to what Marketo's Landing Page Conversion Report numbers are, and GA's is still significantly lower. For example, GA's goal completions is 25 whereas Marketo says 33 - this is over the span of 3 hours only and there's already this gap. I was hoping the missing eventCallback was the key but am I missing something else @SanfordWhiteman ? Any ideas or insights would be greatly appreciated!
. For example, GA's goal completions is 25 whereas Marketo says 33 - this is over the span of 3 hours only and there's already this gap. I was hoping the missing eventCallback was the key but am I missing something else...
We’ll take Marketo’s count as the truth, since the server counts actual form fills.
So let’s think about what can make a Marketo form post complete successfully, while a a relayed GA event in an onSuccess listener doesn’t complete, in rough order of descending likelihood:
What’s the URL that’s displaying this behavior?
Thanks @SanfordWhiteman - the discrepancies have been on many different URLs. Here are a few examples below. I included the conversions numbers from Marketo report, as well as 3 different metrics in GA - I pulled both the Goal Completion, as well as the number of Events by page (Event Category = Marketo).
Not sure why unique events are still higher than goal completions in GA, when the goal logic itself is the same as the filtered view of the Events in GA. Would love any insights you have to this mind boggler!
URL (period Nov 3 - Nov 6) | Marketo Conversions | GA Goal Completions | GA Total Events | GA Unique Events |
https://info.wegalvanize.com/sox-management-checklist.html | 141 | 122 | 136 | 125 |
https://info.wegalvanize.com/Death-of-the-Tick-Mark_LP.html | 88 | 76 | 88 | 82 |
https://info.wegalvanize.com/internal-audit-impact-series.html | 70 | 58 | 66 | 63 |
Sorry for the late reply @SanfordWhiteman - for some reason I'm not getting email alerts when someone replies anymore. I did spot check a couple in Marketo using a smart list 'filled out form' to take out any duplicates...in some cases it reduces the number but a few but not enough to match closer with GA Goals.
There is one landing page I noticed that isn't even pulling any goal completions or events in GA, even though the GTM tag is on the page and Marketo says there are 14 submissions: https://info.wegalvanize.com/auditors-critical-to-vendor-onboarding-webinar.html
Would you have any insights into this? THanks!
What was the reason that the setTimeout isn't the best idea? I literally just took the script from the blog so not tied to it.
Just because JS is an event-driven environment so you should listen for the real event “error message shown” instead of guessing about when it will be shown.
In this case, though, the setTimeout is being used to wait for the next turn of the event loop (setTimeout(fn,0)) rather than an arbitrary number of milliseconds. That’s not bad, as long as the library keeps behaving as it currently does (an assumption I make all the time, so shouldn’t have been as harsh about it in this case!).