Skill Tracer
Skills › Marketing Skills › conversion-tracking › detect-formidable-forms.json

detect-formidable-forms.json

103 lines · skills/conversion-tracking/assets/gtm-recipes/detect/detect-formidable-forms.json at 88c79f6

1{
2 "exportFormatVersion": 2,
3 "exportTime": "2026-07-02 00:00:00",
4 "containerVersion": {
5 "path": "accounts/0/containers/0/versions/0",
6 "accountId": "0",
7 "containerId": "0",
8 "containerVersionId": "0",
9 "container": {
10 "path": "accounts/0/containers/0",
11 "accountId": "0",
12 "containerId": "0",
13 "name": "Conversion Detection - Formidable Forms",
14 "publicId": "GTM-XXXXXXX",
15 "usageContext": [
16 "WEB"
17 ],
18 "fingerprint": "0",
19 "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/0/containers/0/workspaces/0"
20 },
21 "tag": [
22 {
23 "accountId": "0",
24 "containerId": "0",
25 "tagId": "1",
26 "name": "Conversion Detection - Formidable Forms Submission Listener",
27 "type": "html",
28 "parameter": [
29 {
30 "type": "TEMPLATE",
31 "key": "html",
32 "value": "<script>\n(function () {\n 'use strict';\n\n // Shared with the recipe snippet so template + recipe installation is one listener.\n if (window.__ctDetectFormidableForms) return;\n window.__ctDetectFormidableForms = true;\n if (!/^https?:$/.test(window.location.protocol)) return;\n window.dataLayer = window.dataLayer || [];\n\n var MARKER_KEY = '__ctDetectFormidableNativePendingV2';\n var MARKER_TTL_MS = 60000;\n var handledResponses = typeof WeakSet === 'function' ? new WeakSet() : [];\n var activeAttempts = [];\n var jqueryHooked = false;\n\n function safeId(value) {\n var id = String(value || '');\n return /^[1-9][0-9]*$/.test(id) ? id : '';\n }\n\n // Formidable's hidden form_id is non-sensitive structural metadata. No other\n // field value, response property, label, HTML, or rendered text is inspected.\n function formId(form) {\n if (!form || typeof form.querySelector !== 'function') return '';\n try {\n var input = form.querySelector('input[name=\"form_id\"]');\n return input && input.getAttribute ? safeId(input.getAttribute('value')) : '';\n } catch (e) {\n return '';\n }\n }\n\n function wrapperFor(id) {\n if (!id) return null;\n try { return document.getElementById('frm_form_' + id + '_container'); } catch (e) { return null; }\n }\n\n function hasNode(scope, selector) {\n if (!scope || typeof scope.querySelector !== 'function') return false;\n try { return !!scope.querySelector(selector); } catch (e) { return false; }\n }\n\n function hasRejectionMarker(id) {\n return hasNode(wrapperFor(id), '.frm_error, .frm_error_style, .frm_blank_field, [aria-invalid=\"true\"]');\n }\n\n function hasSuccessMarker(id) {\n return hasNode(wrapperFor(id), '.frm_message');\n }\n\n function responseWasHandled(response) {\n if (!response || (typeof response !== 'object' && typeof response !== 'function')) return true;\n if (typeof WeakSet === 'function' && handledResponses instanceof WeakSet) {\n if (handledResponses.has(response)) return true;\n handledResponses.add(response);\n return false;\n }\n for (var i = 0; i < handledResponses.length; i++) {\n if (handledResponses[i] === response) return true;\n }\n handledResponses.push(response);\n if (handledResponses.length > 100) handledResponses.shift();\n return false;\n }\n\n function push(id) {\n if (!id) return;\n window.dataLayer.push({ event: 'formidable_forms_submitted', form_id: id });\n }\n\n // Optional Method 2 adapter owns any enriched push. The detector passes only\n // structural metadata and accepts only a literal true handled verdict; it\n // never receives or inspects an adapter payload.\n function emitAccepted(id, form, source) {\n var handled = false;\n try {\n if (typeof window.__ctFormidableMethod2Emit === 'function') {\n handled = window.__ctFormidableMethod2Emit(id, form, source) === true;\n }\n } catch (e) {}\n if (!handled) push(id);\n }\n\n // In 6.32.1 these are jQuery events: formidable.js calls\n // jQuery(document).trigger(name, [form, response]). addEventListener does not\n // receive these success events. The response is used only by object identity\n // so the redirect and completion callbacks for one response count once.\n function onAjaxSuccess(event, form, response) {\n var id = formId(form);\n if (!id || !response || responseWasHandled(response)) return;\n emitAccepted(id, form, 'ajax');\n disarmAttempt(form);\n }\n\n function hookJquery() {\n if (jqueryHooked) return true;\n var jq = window.jQuery || window.$;\n if (typeof jq !== 'function') return false;\n jq(document).on(\n 'frmFormComplete.ctRecipe frmBeforeFormRedirect.ctRecipe',\n onAjaxSuccess\n );\n jqueryHooked = true;\n return true;\n }\n\n function readMarker() {\n try {\n var raw = window.sessionStorage.getItem(MARKER_KEY);\n if (!raw) return null;\n window.sessionStorage.removeItem(MARKER_KEY);\n var marker = JSON.parse(raw);\n if (!marker || typeof marker !== 'object') return null;\n if (typeof marker.t !== 'number' || Date.now() < marker.t || Date.now() - marker.t > MARKER_TTL_MS) return null;\n if (marker.hadSuccessAtSubmit === true) return null;\n return { id: safeId(marker.id), origin: marker.origin, path: marker.path };\n } catch (e) {\n try { window.sessionStorage.removeItem(MARKER_KEY); } catch (e2) {}\n return null;\n }\n }\n\n // Native success either renders a form-specific message or redirects away\n // from the submitted form. Rejection stays on the form page with errors.\n // Only origin and pathname are retained, never query strings or form answers.\n function processNativeMessage() {\n var marker = readMarker();\n if (!marker || !marker.id || hasRejectionMarker(marker.id)) return;\n var redirected = marker.origin === window.location.origin &&\n typeof marker.path === 'string' && marker.path !== window.location.pathname &&\n !wrapperFor(marker.id) && !hasNode(document, '.frm_error, .frm_error_style');\n if (hasSuccessMarker(marker.id) || redirected) {\n var wrapper = wrapperFor(marker.id);\n var form = null;\n try { form = wrapper && wrapper.querySelector ? wrapper.querySelector('form') : null; } catch (e) {}\n emitAccepted(marker.id, form, 'native');\n }\n }\n\n function getAttempt(form) {\n for (var i = 0; i < activeAttempts.length; i++) {\n if (activeAttempts[i].form === form) return activeAttempts[i];\n }\n return null;\n }\n\n function disarmAttempt(form) {\n var attempt = getAttempt(form);\n if (!attempt) return;\n try { window.removeEventListener('pagehide', attempt.onPageHide, true); } catch (e) {}\n var index = activeAttempts.indexOf(attempt);\n if (index !== -1) activeAttempts.splice(index, 1);\n }\n\n function armNativeMessageAttempt(event) {\n var form = event && event.target;\n if (!form || String(form.tagName || '').toLowerCase() !== 'form') return;\n var isFormidable = false;\n try { isFormidable = !!(form.closest && form.closest('.frm_forms')); } catch (e) {}\n if (!isFormidable) return;\n var id = formId(form);\n if (!id) return;\n var wrapper = wrapperFor(id);\n var hadSuccessAtSubmit = hasNode(wrapper, '.frm_message');\n var prior = getAttempt(form);\n if (prior) disarmAttempt(form);\n var attempt = { form: form, id: id, hadSuccessAtSubmit: hadSuccessAtSubmit, onPageHide: null };\n attempt.onPageHide = function () {\n disarmAttempt(form);\n // Old server errors remain visible while a corrected native POST leaves.\n // Decide acceptance from the response page, not the departing page.\n if (attempt.hadSuccessAtSubmit) return;\n try {\n window.sessionStorage.setItem(MARKER_KEY, JSON.stringify({\n t: Date.now(),\n id: attempt.id,\n hadSuccessAtSubmit: false,\n origin: window.location.origin,\n path: window.location.pathname\n }));\n } catch (e) {}\n };\n activeAttempts.push(attempt);\n try { window.addEventListener('pagehide', attempt.onPageHide, true); } catch (e) {}\n }\n\n hookJquery();\n if (!jqueryHooked) {\n document.addEventListener('DOMContentLoaded', hookJquery, { once: true });\n window.addEventListener('load', hookJquery, { once: true });\n }\n document.addEventListener('submit', armNativeMessageAttempt, true);\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', processNativeMessage, { once: true });\n } else {\n processNativeMessage();\n }\n})();\n</script>"
33 },
34 {
35 "type": "BOOLEAN",
36 "key": "supportDocumentWrite",
37 "value": "false"
38 }
39 ],
40 "firingTriggerId": [
41 "2147479553"
42 ],
43 "tagFiringOption": "ONCE_PER_EVENT",
44 "monitoringMetadata": {
45 "type": "MAP"
46 },
47 "consentSettings": {
48 "consentStatus": "NOT_SET"
49 }
50 }
51 ],
52 "trigger": [
53 {
54 "accountId": "0",
55 "containerId": "0",
56 "triggerId": "2",
57 "name": "CE - Formidable Forms Submitted",
58 "type": "CUSTOM_EVENT",
59 "customEventFilter": [
60 {
61 "type": "EQUALS",
62 "parameter": [
63 {
64 "type": "TEMPLATE",
65 "key": "arg0",
66 "value": "{{_event}}"
67 },
68 {
69 "type": "TEMPLATE",
70 "key": "arg1",
71 "value": "formidable_forms_submitted"
72 }
73 ]
74 }
75 ],
76 "filter": []
77 }
78 ],
79 "variable": [
80 {
81 "accountId": "0",
82 "containerId": "0",
83 "variableId": "1",
84 "name": "DLV - Formidable Form ID",
85 "type": "v",
86 "parameter": [
87 {
88 "type": "TEMPLATE",
89 "key": "name",
90 "value": "form_id"
91 },
92 {
93 "type": "INTEGER",
94 "key": "dataLayerVersion",
95 "value": "2"
96 }
97 ]
98 }
99 ],
100 "fingerprint": "0",
101 "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/0/containers/0/workspaces/0"
102 }