Cookie banners are a tax on reading. Accepting everything is one click. Rejecting often means finding a smaller button, or opening a settings screen and switching toggles off one at a time. I got tired of doing that by hand, so I wrote something to do it for me.
Cookie Cutter is a Chrome extension that finds the reject button on a cookie banner and clicks it. It's on GitHub at v1.3.0, and you install it unpacked from there. It isn't on the Chrome Web Store. Here's how it finds the button, and the bug that made me rethink how it matches.
Small on purpose
There's no framework and no build step. It's one content script, one service worker and a popup, all plain JavaScript on Manifest V3. The content script runs at document_idle on every page, scans for a banner, and if nothing turns up, watches the DOM with a MutationObserver for 15 seconds before giving up. Banners often arrive late, after a consent script loads, so a single scan at page load would miss them.
Keeping it this small means anyone can read the whole thing in an evening, which matters for a tool that runs on every page you open.
Selectors first, text second
Most banners come from a handful of consent platforms, and each one usually names its reject button the same way on every site. OneTrust uses #onetrust-reject-all-handler, Cookiebot uses #CybotCookiebotDialogBodyButtonDecline, Didomi uses #didomi-notice-disagree-button. So the extension tries a list of known selectors first. It covers OneTrust, Cookiebot, Didomi, Quantcast, TrustArc and CookieYes, and about a dozen more.
When no selector matches, it falls back to button text: reject all, decline all, only necessary, no thanks, and plain reject or decline at the end of the list. It searches buttons, links and inputs, and descends into open shadow roots for banners built as web components.
The bug that clicked Decline invitation
The first version had one more fallback. If the banner had no matching button, it searched the whole page by text. Single words like reject and decline match a lot of buttons that have nothing to do with cookies. On a GitHub profile it clicked Decline invitation and the page redirected.
The fix was to stop trusting the selectors alone. Broad ones like [class*='consent'] match plenty of non-cookie UI. Now an element only counts as a banner if it carries a known consent platform id or class, or its text mentions cookies or GDPR. The text search only ever runs inside that element, and the whole-page search is gone. The last-resort scan of role="dialog" elements went through the same check, because privacy and consent on their own matched unrelated dialogs.
I'd rather the extension miss a banner than click something on a page it doesn't understand.
Banners inside iframes
Didomi, Quantcast and Google's consent screen can render inside an iframe, where a top-level content script can't reach. So in 1.3.0 the content script runs with all_frames set to true. That brought two problems. A single rejection could fire from several frames, so the service worker drops duplicates from the same tab and host within two seconds. And frames report their own origin, which is often a consent or ad host, so each frame resolves the top-level hostname before reporting, and only the top frame is allowed to log a failure.
When there's no reject button
Some banners don't offer one. If a banner was found and nothing was clicked after 15 seconds, the site goes into a Failed list in the popup. Each entry has a Report link that opens a prefilled GitHub issue with the hostname, the extension version, the user agent and a timestamp. It doesn't include the page URL.
There's also a last resort, on by default and switchable in the popup. It hides the banner and its backdrop, and sets overflow back to auto on the html and body elements, because some consent scripts lock scrolling until you choose. That isn't a rejection, and the site never gets an answer. It only gets the banner out of the way so you can read.
The rest of the popup
The popup shows a total, today's count and the recent sites, in a light and dark theme that follow your OS setting. You can pause it per site, toggle it with Alt+Shift+C, and export your stats as JSON or CSV. It also sends the Global Privacy Control signal, as a Sec-GPC header through declarativeNetRequest and as navigator.globalPrivacyControl in the page.
New selectors don't need a release. A rules.json file in the repo gets fetched at most once a day, validated, and merged in. Selectors that are too broad, like body or anything with :has(, are thrown out. Adding a consent platform is usually a one-line pull request.
Where it leaves me
Cookie Cutter does the clicking I used to do by hand, and when it can't, the popup tells me which site to report. If your team has a browser chore like this that someone repeats every day, that's the kind of tool I build.





