Skills › Marketing Skills › conversion-tracking › detect-ninja-forms.json
detect-ninja-forms.json
103 lines · skills/conversion-tracking/assets/gtm-recipes/detect/detect-ninja-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 - Ninja 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 - Ninja Forms Submission Listener",
27 "type": "html",
28 "parameter": [
29 {
30 "type": "TEMPLATE",
31 "key": "html",
32 "value": "<script>\n(function () {\n 'use strict';\n var runtime = window.__ctNinjaFormsRuntime;\n if (runtime && runtime.installed) return;\n\n // Preserve the recipe's public duplicate-install guard. If a superseded\n // snippet already set it without this runtime, fail closed: don't layer a\n // second listener over an unknown old detector.\n if (!runtime && window.__ctDetectNinjaForms) return;\n\n if (!runtime) {\n runtime = { seenEntryIds: Object.create(null), installed: false };\n window.__ctNinjaFormsRuntime = runtime;\n }\n window.__ctDetectNinjaForms = true;\n window.dataLayer = window.dataLayer || [];\n\n var MISSING = {};\n var MAX_SAFE_INTEGER = 9007199254740991;\n\n // Read only own data properties. Callback payloads are normally parsed JSON,\n // but a filter or another page script can supply accessor-backed objects.\n // Do not execute getters while deciding whether a response proves a save.\n function ownDataValue(object, key) {\n if (!object || typeof object !== 'object' || !Object.prototype.hasOwnProperty.call(object, key)) return MISSING;\n if (typeof Object.getOwnPropertyDescriptor !== 'function') return MISSING;\n var descriptor = Object.getOwnPropertyDescriptor(object, key);\n if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) return MISSING;\n return descriptor.value;\n }\n\n function emptyErrors(value) {\n if (Object.prototype.toString.call(value) === '[object Array]') return value.length === 0;\n if (!value || typeof value !== 'object') return false;\n for (var key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) return false;\n }\n return true;\n }\n\n // Accept only primitive safe integers or canonical decimal strings. Never\n // coerce booleans, arrays, objects, boxed numbers, or accessor-backed IDs.\n function positiveIntegerId(value) {\n if (typeof value === 'number') {\n if (!isFinite(value) || value <= 0 || Math.floor(value) !== value || value > MAX_SAFE_INTEGER) return null;\n return String(value);\n }\n if (typeof value !== 'string' || !/^[1-9]\\d*$/.test(value)) return null;\n var number = Number(value);\n if (!isFinite(number) || number > MAX_SAFE_INTEGER || String(number) !== value) return null;\n return value;\n }\n\n function canonicalFormId(value) {\n var formId;\n if (typeof value === 'number') {\n if (!isFinite(value) || value <= 0 || Math.floor(value) !== value || value > MAX_SAFE_INTEGER) return null;\n formId = String(value);\n } else if (typeof value === 'string') {\n formId = value;\n } else {\n return null;\n }\n if (!/^[1-9]\\d*(?:_(?:0|[1-9]\\d*))*$/.test(formId)) return null;\n var parts = formId.split('_');\n for (var i = 0; i < parts.length; i++) {\n var part = Number(parts[i]);\n if (!isFinite(part) || part > MAX_SAFE_INTEGER || String(part) !== parts[i]) return null;\n }\n return formId;\n }\n\n function inspectResponse(response, callbackFormId) {\n var errors = ownDataValue(response, 'errors');\n if (errors === MISSING || !emptyErrors(errors)) return null;\n var data = ownDataValue(response, 'data');\n if (!data || typeof data !== 'object') return null;\n var halt = ownDataValue(data, 'halt');\n if (halt !== MISSING && halt) return null;\n var nestedErrors = ownDataValue(data, 'errors');\n if (nestedErrors !== MISSING && !emptyErrors(nestedErrors)) return null;\n var formValue = ownDataValue(data, 'form_id');\n var formId = formValue === MISSING ? null : canonicalFormId(formValue);\n if (!formId || canonicalFormId(callbackFormId) !== formId) return null;\n var actions = ownDataValue(data, 'actions');\n if (!actions || typeof actions !== 'object') return null;\n var save = ownDataValue(actions, 'save');\n if (!save || typeof save !== 'object') return null;\n var saveValue = ownDataValue(save, 'sub_id');\n var entryId = saveValue === MISSING ? null : positiveIntegerId(saveValue);\n if (!entryId) return null;\n return { formId: formId, entryId: entryId };\n }\n\n runtime.inspectResponse = inspectResponse;\n\n function handleSubmitResponse(response, textStatus, jqXHR, callbackFormId) {\n var accepted = inspectResponse(response, callbackFormId);\n if (!accepted) {\n if (typeof window.__ctNinjaMethod2AfterResponse === 'function') {\n window.__ctNinjaMethod2AfterResponse(callbackFormId, false, false);\n }\n return;\n }\n\n var key = '$' + accepted.entryId;\n if (runtime.seenEntryIds[key]) {\n if (typeof window.__ctNinjaMethod2AfterResponse === 'function') {\n window.__ctNinjaMethod2AfterResponse(accepted.formId, false, true);\n }\n return;\n }\n runtime.seenEntryIds[key] = true;\n\n var enriched = false;\n if (typeof window.__ctNinjaMethod2Emit === 'function') {\n try {\n enriched = window.__ctNinjaMethod2Emit(accepted.formId, accepted.entryId) === true;\n } catch (e) {\n enriched = false;\n }\n }\n if (!enriched) {\n window.dataLayer.push({ event: 'ninja_forms_submitted', form_id: accepted.formId });\n }\n if (typeof window.__ctNinjaMethod2AfterResponse === 'function') {\n window.__ctNinjaMethod2AfterResponse(accepted.formId, true, false);\n }\n }\n\n function hookNfRadio() {\n if (runtime.installed) return true;\n var radio = typeof nfRadio !== 'undefined' && nfRadio.channel ? nfRadio :\n (typeof Backbone !== 'undefined' && Backbone.Radio ? Backbone.Radio : null);\n if (!radio) return false;\n try {\n radio.channel('forms').on('submit:response', handleSubmitResponse);\n runtime.installed = true;\n return true;\n } catch (e) {\n return false;\n }\n }\n\n if (!hookNfRadio()) {\n document.addEventListener('DOMContentLoaded', hookNfRadio);\n window.addEventListener('load', hookNfRadio);\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 - Ninja 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": "ninja_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 - Ninja Forms 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 }
103}