Imoran ai LogoImoran ai Logo

Building Cookie Cutter: A Chrome Extension That Clicks Reject for You

Why I built a small Chrome extension that finds the reject button on cookie banners and clicks it, how it avoids clicking the wrong button, and what it does when a banner has no reject option at all.

Published: November 2, 2026

Last updated: November 4, 2026

The Cookie Cutter popup in dark mode, showing 47 banners rejected, five recent sites, one failed site with a Report link and the per-site pause and hide controls.

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.

Frequently asked questions

Which cookie banners does Cookie Cutter handle?

It has selectors for OneTrust, Cookiebot, Didomi, Quantcast, TrustArc, CookieYes, Iubenda, Osano, Complianz, Borlabs, Google and YouTube consent, PostHog and several more. Anything else falls back to matching button text like reject all or decline, but only inside an element that is confirmed to be a cookie banner.

How do I install it?

It isn't on the Chrome Web Store. Clone or download the repo from GitHub, open chrome://extensions, turn on Developer mode, click Load unpacked and pick the folder.

What happens when it can't find a reject button?

After 15 seconds the site shows up in the popup's Failed list with a Report link that opens a prefilled GitHub issue. If the hide uncloseable banners setting is on, it also hides the banner and restores page scroll.

Does it send any data anywhere?

Stats stay in Chrome's local extension storage. The extension's own network request is a fetch of the community rules file from the GitHub repo, at most once a day, and the popup loads its Sora font from Google Fonts. A failure report only goes out if you click Report and submit the GitHub issue, and it carries the hostname, the extension version, your user agent and a timestamp.

Can you build something like this for my team?

Yes. If your team keeps doing the same small chore in the browser by hand, a focused extension usually fixes it. Tell me the workflow and I'll map out an approach.

More Articles

Let's Work
Together

Hand Reaching

Have a project in mind? We'd love to hear about it. Let's create something great together!

PRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENTPRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENTPRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENT