Form-submit dataLayer instrumentation for Google Ads Enhanced Conversions
The client's paid-media conversions were tracked via a GTM Element Visibility trigger that carried no lead information, so Google Ads Enhanced Conversions had nothing to match on. The form opens in a modal from the 'Get started' button in a client-rendered Next.js app, making the implementation impossible to verify externally. I authored a precise Claude Code implementation prompt that first inspects the repo read-only, classifies the form as native React or a third-party embed (HubSpot detected via the window 'message' hsFormCallback / onFormSubmit event), then inserts a single additive dataLayer.push of a 'lead_form_submit' event with a normalized user_data object (email lowercased/trimmed, phone in E.164, names trimmed). Raw values are handed to GTM's User-Provided Data variable for SHA-256 hashing rather than hashing client-side. The spec enforces strict additivity, a single-fire guard against React re-render and StrictMode double-invocation, consent gating, and full QA via console and GTM Preview.
Push raw, normalized values; GTM's User-Provided Data variable handles the SHA-256 hashing, not the client.
Role: Sole architect of the tracking design and author of the Claude Code implementation prompt
The Problem
The client's Google Ads Enhanced Conversions could not be fed with lead data because form submissions were being tracked without any user-provided information attached. This limits conversion match quality and downstream bidding/attribution for paid media.
On successful submission of the 'Get started / Contact Sales' form, GTM only fired an Element Visibility trigger that carried no lead fields (email, phone, name). There was no dataLayer event exposing the submitted lead data to GTM's User-Provided Data variable.
The form is rendered client-side in a modal that opens on the 'Get started' button click in a Next.js app, so it could not be inspected from outside; the implementation could be a native React form or a third-party embed (e.g. HubSpot) where fields are only accessible via a postMessage callback. The change also had to be strictly additive so as not to break the existing Element Visibility trigger, and had to guard against double-firing under React re-renders and StrictMode.
Approach & Architecture
Authored a detailed English prompt/spec instructing Claude Code to first inspect the repo read-only and classify the form as native React vs third-party embed, then insert a single additive dataLayer.push at the confirmed success point. The push emits a 'lead_form_submit' event carrying a normalized user_data object. Raw (normalized) values are sent so GTM's User-Provided Data variable performs SHA-256 hashing, avoiding client-side hashing. A single-fire guard prevents duplicate pushes.
Client-side: on successful form submit, window.dataLayer receives one 'lead_form_submit' event with a user_data object (email, phone_number, first_name, last_name, optional address). GTM: a Custom Event trigger keyed to 'lead_form_submit' plus a User-Provided Data variable (type Code) mapping to user_data, feeding the Google Ads Enhanced Conversions tag. Existing Element Visibility trigger left untouched.
Key Decisions & Trade-offs
Hardest Part
Specifying a solution that works without being able to see the form implementation: the prompt had to instruct Claude Code to detect whether the form is native or a third-party embed (HubSpot detection via window 'message' events where data.type === 'hsFormCallback' and data.eventName === 'onFormSubmit') and to hook the correct success point in each case, while remaining strictly additive and single-fire.
Technical Detail
dataLayer schema: event 'lead_form_submit' with a nested user_data object { email, phone_number, first_name, last_name, optional address:{street,city,region,postal_code,country} }. Normalization rules: email trim+lowercase, phone_number stripped and E.164, names trimmed, missing fields omitted.
Existing Next.js/React web application (client repo); no infrastructure changes required by design.
Prescribed QA: confirm the app builds and the form behaves identically; open the modal, submit a test lead, inspect window.dataLayer in the browser console to confirm exactly one 'lead_form_submit' event with a populated user_data object; verify it does not fire on open, on invalid submit, or twice; validate via GTM Preview / Tag Assistant.
Code
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: 'lead_form_submit', user_data: { email: <trimmed, lowercased email>, phone_number: <digits in E.164, e.g. +18095551234>, first_name: <trimmed>, last_name: <trimmed>, // include ONLY fields the form actually collects; omit the rest }
});// window 'message' event where: // data.type === 'hsFormCallback' && data.eventName === 'onFormSubmit' // (payload includes submitted fields), or the form's onFormSubmit callback
The deliverable itself is an AI-native workflow artifact: a precise, constraint-bound English implementation prompt/spec engineered for Claude Code to execute against the live the client repo. The prompt enforces an investigate-first (read-only) step, a strictly additive change, single-fire guards, and explicit do-not-do boundaries, so the coding agent can implement tracking safely without breaking production.
Measured Results
A complete, safety-bounded implementation spec was produced that, once executed, would expose lead data (email, phone, name) to GTM at form-submit time so Google Ads Enhanced Conversions can be fed via a User-Provided Data variable. No implementation metrics exist because the code was designed but not shipped in this chat.