I found my Chrome extension was reading an icon as every campaign's name, then rebuilt it on the official API
▲ 2 r/chrome_extensions+1 crossposts

I found my Chrome extension was reading an icon as every campaign's name, then rebuilt it on the official API

I build KeywordAxe, a Chrome extension for Google Ads users (keyboard shortcuts, spend-anomaly alerts, bulk negative keywords). It has opt-in telemetry, and for months that telemetry quietly showed a core feature running but finding nothing — for everyone.


The cause was a good lesson in the fragility of DOM scraping. The extension read the campaigns table by column position. Google reshuffled that table in 2026: each row now starts with a checkbox and an expand-chevron cell, and the chevron is a Material icon rendered as *ligature text* — the literal string
"expand_more". So my extractor read every campaign's name as "expand_more" and
they all collapsed into one junk record. "Found nothing" and "silently broken"
produce identical output, so nobody noticed — including me — until the telemetry pattern gave it away.


I patched the scraper (read the header row, map columns by name, strip the icon
ligatures), then decided scraping a UI Google can reshuffle anytime was a losing game and rebuilt the data layer on the official Google Ads API — read-only OAuth (PKCE) from an MV3 service worker, with the developer token + client secret in a stateless Cloudflare Worker relay so nothing sensitive ships in the extension.


Stack: vanilla JS, no bundler, MV3, Cloudflare Workers, IndexedDB for the local
fallback. Happy to talk about the scraping→API migration, the MV3 OAuth flow, or the relay design.


Chrome Web Store: https://chromewebstore.google.com/detail/gcgpkjmlpfppmejefedicclpbdfncdfp
u/rktreddy — 1 day ago