Skip to Content

gtm-datalayer Component

gtm-datalayer is a Surface Workflow component that pushes MonetizationOS (MOS) decision and visitor data to the client’s analytics data layer. It runs as a component workflow in parallel with the other components (article-body, demeter), consuming the properties.outcome, properties.visitor, and properties.logs produced by the parent Surface Workflow (see Website Surface Workflow) — it makes no decisions of its own.

Like all Surface Workflow components, gtm-datalayer is authored and configured inside MOS (the surface editor) per client, not part of the Cloudflare Worker repository. The code below is the reference implementation a client copies in and adapts.

Responsibilities

  • Push visitorType, decision_source, and is_sophi_script_blocked to the data layer for downstream analytics on every page.
  • Push a sophi_wall_shown event, with the wall type, when a paywall or regwall is displayed (outcome.wallVisibility === 'always' and outcome.wallType !== 'none').

Output

A before content element: a single <script> tag prepended to the page that performs the data-layer pushes described above. Nothing is rendered when debug logging is off.

Extension Points

Configurable data-layer target

  • Purpose: let a client push to whatever data layer object/method their analytics stack actually uses, instead of assuming Google Tag Manager’s window.dataLayer.
  • Input: a client-defined data-layer variable name and push pattern (e.g. a different global, or a function call instead of an array push).
  • Default starting point: the reference implementation below always targets the standard GTM pattern — window.dataLayer = window.dataLayer || []; window.dataLayer.push(event...).
  • Status: no default abstraction is provided yet — a client that needs a non-GTM data layer replaces dataLayerScript below directly.

Debug Logging

When env.ENABLE_LOGGING === 'debug', two extra console.log elements are emitted before the data-layer script: one summarizing this component’s own inputs/outputs (surfaceProperties.visitor and surfaceProperties.outcome, plus the wallShown flag and what the push will contain), and one dumping the parent Surface Workflow’s full properties.logs decision trail, so the two can be cross-referenced in the browser console.

Reference Implementation

// ─── GTM DataLayer Component ────────────────────────────────────────────────── // // Manages interactions with the Google Tag Manager dataLayer. Runs as a // component workflow in parallel with other components (article-body, demeter). // // Responsibilities: // - Push visitorType, decision_source, and is_sophi_script_blocked to the dataLayer // for downstream analytics // - Push a sophi_wall_shown event when a paywall or regwall is displayed type WebElement = { type: 'html'; content: string }; const workflow: SurfaceWorkflow = async ({ surfaceProperties, env }) => { const debug = env.ENABLE_LOGGING === 'debug'; // ─── Read visitor info from Surface Workflow ────────────────────────────── const visitor = (surfaceProperties?.visitor ?? {}) as Record<string, unknown>; const visitorType = String(visitor.visitorType ?? 'anonymous'); // Upstream workflow emits a real boolean; narrow explicitly since visitor is untyped here const isSophiScriptBlocked = visitor.isSophiScriptBlocked === true // ─── Read the Surface Workflow's access decision ────────────────────────── const outcome = (surfaceProperties?.outcome ?? {}) as Record<string, unknown>; const wallVisibility = String(outcome.wallVisibility ?? 'never'); const wallType = String(outcome.wallType ?? 'none'); const decisionSource = String(outcome.decisionSource ?? 'override'); const wallShown = wallVisibility === 'always' && wallType !== 'none'; const elements: WebElement[] = []; if (debug) { const payload = JSON.stringify({ component: 'gtm-datalayer', surfaceProperties: { visitor: { visitorType, isSophiScriptBlocked }, outcome: { wallVisibility, wallType, decisionSource }, }, flags: { wallShown }, output: `dataLayer.push with visitorType + decision_source + is_sophi_script_blocked${wallShown ? ' + sophi_wall_shown event' : ''}`, }); elements.push({ type: 'html', content: `<script>console.log("[MOS Debug] gtm-datalayer", ${payload});</script>` }); // Surface Workflow's full decision trail (meter values, cookie state, path taken) const logsPayload = JSON.stringify(surfaceProperties?.logs ?? {}); elements.push({ type: 'html', content: `<script>console.log("[MOS Debug] surfaceProperties.logs", ${logsPayload});</script>` }); } elements.push(dataLayerScript(visitorType, decisionSource, isSophiScriptBlocked, wallShown ? wallType : undefined)); return { content: { before: elements, }, }; }; // ─── Helpers ────────────────────────────────────────────────────────────────── /** * Builds a `<script>` element that pushes visitor metadata to the GTM dataLayer. * Includes decision_source and is_sophi_script_blocked for observability. * When a wall is shown, also pushes a `sophi_wall_shown` event with the wall type. */ function dataLayerScript(visitorType: string, decisionSource: string, isSophiScriptBlocked: boolean, wallType?: string): WebElement { const wallPush = wallType ? `\nwindow.dataLayer.push(${JSON.stringify({ event: 'sophi_wall_shown', wall_type: wallType })});` : ''; return { type: 'html', content: `<script> window.dataLayer = window.dataLayer || []; window.dataLayer.push(${JSON.stringify({ visitorType, decision_source: decisionSource, is_sophi_script_blocked: isSophiScriptBlocked })});${wallPush} </script>`, }; } export default workflow;
Last updated on