Client: International Salon Supplies (internal tooling)
Role: Developer
Period: Jul 2023 – Feb 2025
Status: Internal tool
Tech stack: Chrome Extension (Manifest V3), JavaScript, Google Tag Manager, GA4, Chrome Side Panel API, chrome.storage
A Chrome extension that watches every dataLayer.push on the storefront in real time, making GA4 and Google Tag Manager e-commerce tracking easy to verify. The rewrite fixed a subtle bug where the first version could interfere with GTM's own handler, and moved the UI into Chrome's Side Panel. Plus a tiny extension that fixed a recurring CRM data-entry problem.
The problem
E-commerce analytics only works if the right events fire with the right data: view_item, add_to_cart, begin_checkout, purchase. Checking that in the browser console is tedious and easy to get wrong. I wanted a live, readable feed of every dataLayer event while clicking through the store.
Version 1: an on-page panel
- A content script runs at
document_startand injects a script into the page itself. Content scripts live in an isolated world and can't see the page'swindow.dataLayer. - The injected script wraps
dataLayerin a JavaScript Proxy that interceptspushand forwards each event to the extension withwindow.postMessage. - The content script checks that each message came from the same window, then renders it in a floating panel (newest first, capped at 100 entries, with clear and collapse buttons) and in grouped console logs.
The subtle bug
Google Tag Manager also replaces dataLayer.push with its own handler when it loads. v1's Proxy always returned its own wrapper for push, so GTM's assignment reached the underlying array but was never read back. In effect, the monitor could hide GTM's handler and interfere with the tracking it was supposed to observe.
Version 2: wrap, don't replace
- The injected script polls every 100 ms (for up to 10 s) until
dataLayerexists, then wraps the existingpushand calls through to it. GTM's handler stays in the chain. - Events pushed before the wrap are replayed, so nothing is missed.
- Errors inside the hook fall back to the original
push. The monitor can never break the page.
v2 also moved the UI into Chrome's Side Panel, so it no longer shares the page's DOM or CSS:
- The side panel keeps a long-lived connection to the background worker, which relays live events to every open panel.
- The last 100 events persist in
chrome.storage.local, so a newly opened panel shows history immediately.
A smaller tool: fixing a CRM field that kept resetting
In the marketing CRM, campaign "From name" and "From email" fields kept reverting to the wrong sender, and staff had to correct them by hand. A 41-line extension watches for those fields on the relevant pages and sets the correct values. It dispatches input and change events rather than just setting .value, so the page's own framework registers the change instead of only the visible DOM.
Still on the list
- Render event data with
textContentinstead ofinnerHTML. - Remove a leftover popup that the side-panel flow no longer uses.
Takeaway
When you instrument someone else's code, observe without changing behaviour. The v1 → v2 rewrite came down to one principle: wrap and call through, never replace.