How to Bulk Delete Chrome History by Specific Domains
Here's a claim that'll get me in trouble: Chrome's history management is deliberately bad. Not neglected, not deprioritized. Deliberately bad. Google has zero incentive to make it easy for you to surgically remove traces of specific domains from your browsing history, because your browsing history is their product. The harder it is to manage locally, the more likely you are to just leave it alone, synced up to your Google account, feeding the ad machine.
Now, do I have a leaked internal memo proving this? No. But I've spent six months obsessively managing my browsing data with various tools, and the more time I spend with Chrome's built-in history, the more convinced I become that the friction is a feature, not a bug.
You're here because you want to delete all history entries from specific domains. Maybe you were debugging on staging servers all week. Maybe you hit a rabbit hole of Stack Overflow pages for a framework you ended up not using. Maybe you just want reddit.com gone from the last three months because your history looks embarrassing. Whatever the reason, Chrome makes this absurdly hard. Let me show you what I mean, and then what actually works.
The Ctrl+H problem
Open chrome://history. You get a reverse-chronological list. There's a search bar at the top. You can type a domain name like stackoverflow.com and it'll filter results. Great, right?
Not really.
Here's what happens next: you see results 150 at a time. You can check individual boxes. You can hit "Delete." Then Chrome loads the next batch. You check more boxes. Delete. Repeat. If you visited a domain 2,000 times over a few months (which is nothing for a developer hitting docs sites or localhost), you're looking at clicking through 13+ pages of results, manually selecting entries on each one, and confirming deletion each time.
There's no "select all" for filtered results. There's no "delete all matching entries." There's no bulk operation whatsoever. You get checkboxes. One page at a time.
I timed myself once. Clearing about 400 entries from a single domain took me eleven minutes of mindless clicking. Eleven minutes. For one domain. If you've got five or six domains to purge, congratulations, that's your lunch break.
"Just clear all history" is not a solution
Someone always suggests this. Just nuke everything. Settings → Clear browsing data → All time → Browsing history → Clear data.
Sure, if you don't care about anything you've visited. But most developers I know (myself included) actually rely on their history. I've written before about why Chrome's built-in history falls short, and one of the core issues is that once it's gone, it's gone. Chrome doesn't do granular. It's either "delete this one entry" or "delete literally everything." The middle ground, deleting everything from specific domains, doesn't exist natively.
Some people try the nuclear option of deleting the History SQLite file directly. Which, okay, you're a developer, you can do that. But let me walk through why even that approach is more painful than it should be.
The SQLite route (for the brave)
Chrome stores history in a SQLite database. On macOS it's at ~/Library/Application Support/Google/Chrome/Default/History. On Linux, ~/.config/google-chrome/Default/History. Windows, %LOCALAPPDATA%\Google\Chrome\User Data\Default\History.
You need to close Chrome completely before touching this file. Not "close the window." Fully quit. Chrome locks the database.
Then you can do something like:
DELETE FROM urls WHERE url LIKE '%stackoverflow.com%';
DELETE FROM visits WHERE url NOT IN (SELECT id FROM urls);This works. Technically. But there are problems:
- You're modifying Chrome's internal database directly, which means if the schema changes in an update, your script breaks silently or corrupts things.
- The
visitstable uses foreign keys referencingurls, so you need to clean up both tables (and potentiallyvisit_source,keyword_search_terms, and others depending on your Chrome version). - You have to quit Chrome every single time. No background operation.
- There's no undo. Typo in your LIKE clause? Hope you made a backup.
I actually wrote a bash script to automate this about a year ago. It worked for three months, then a Chrome update changed something in the visit chain table structure and I started getting corruption warnings. Stopped using it immediately.
Honestly, for a one-off cleanup it's fine. As a regular workflow? Terrible.
What about other extensions?
I've tried a few history management extensions. Most of them fall into two categories: they either wrap Chrome's existing chrome.history API (which has the same limitations, just with a slightly nicer UI), or they request permissions that make me deeply uncomfortable.
The chrome.history.deleteUrl() API can delete all visits to a specific URL. Not domain. URL. So if you visited docs.example.com/api/v2/auth and docs.example.com/api/v2/users, those are two separate delete calls. You'd need to first search for all URLs matching a domain pattern using chrome.history.search(), then loop through and delete each one.
A few extensions do exactly this. The problem is chrome.history.search() has a maxResults parameter that defaults to 100 and caps at... well, it's technically uncapped, but large numbers get slow and Chrome can stall. For domains you visit frequently, this becomes a real performance issue inside the extension.
And none of this touches the content of those pages. Chrome's API lets you delete the URL record, but if you're using any tool that indexed the page content, that's a separate cleanup.
How TraceMind handles this differently
I should be upfront: I use TraceMind daily and I'm clearly biased. But the reason I started using it wasn't the search features (though those are great). It was the database management.
TraceMind maintains its own index in IndexedDB, separate from Chrome's history. Everything stays local on your machine. When you want to manage what's stored, you're working with TraceMind's database, which was actually designed for this kind of operation.
Here's what bulk deletion by domain looks like:
Open TraceMind's dashboard. Go to the analytics view. You can see a breakdown of stored pages by domain, sorted by frequency or recency. Find the domain you want to purge. Select it. Hit delete. Every page, every snapshot, every indexed content chunk from that domain: gone. One action.
No pagination. No clicking through batches of 150. No SQLite surgery.
Want to delete entries from multiple domains? Select multiple. Same operation. I've cleared out five staging server domains in about ten seconds. The same cleanup that took me 45+ minutes in native Chrome.
The "excluded domains" approach (prevent instead of cure)
What bugs me about the delete-after-the-fact workflow is that it's reactive. You're always cleaning up messes after they happen.
TraceMind has an excluded domains feature that takes the opposite approach. You specify domains you never want indexed, and TraceMind simply ignores them. It never captures the page content, never stores screenshots, never adds them to your searchable index.
The free tier gives you 3 excluded domains. Enough for localhost, maybe your company's internal tools domain, and one more. The Pro tier makes it unlimited, which is what I use. I've got about 15 domains excluded: various staging environments, internal dashboards, a few sites I visit but never need to search later.
You can check out the pricing page if you're curious about the tiers, but the point is: setting this up once means you never need to bulk-delete those domains later. Prevention over cleanup.
A practical workflow for developers
Here's what I actually do. This is my real process, imperfect and opinionated.
Weekly-ish maintenance: I glance at TraceMind's analytics to see if any domain has accumulated a weird number of entries. Sometimes I go deep on a documentation site for a library I'm evaluating, decide against using it, and want to clear the clutter. That's a 30-second bulk delete.
Project transitions: When I finish a project, I'll clean out the staging and dev domains. This is partly practical (I don't need those pages searchable anymore) and partly psychological. Clean slate.
The exclude list grows over time. Every month or two I notice a domain showing up that I'll never need to search and I add it to the exclusion list. It's like a .gitignore for your browsing history. Took me a while to get it dialed in, but now my TraceMind index is remarkably clean.
For Chrome's native history, I do basically nothing. I've accepted it's a mess. I use TraceMind for any serious history search (the semantic search alone makes native Chrome history feel like grep on a novel), and I treat Chrome's built-in Ctrl+H as a last resort for things that happened in the last hour.
What about Chrome's native history and synced data?
Important caveat: TraceMind manages its own index. Deleting a domain's entries in TraceMind does not delete them from Chrome's native history or from your Google account's synced activity.
If you need entries gone from Chrome's actual history (for privacy reasons, say), you still need to deal with Chrome's tools. TraceMind doesn't have permission to modify Chrome's history database, and honestly, I think that's the right call. An extension that can silently delete your Chrome history is an extension that malware could abuse in terrifying ways.
So the workflow is layered:
- TraceMind handles your useful searchable archive, with proper domain-level controls.
- Chrome's native history gets the clunky manual treatment when you need it cleaned.
- Google's "My Activity" dashboard (myactivity.google.com) is a whole separate thing you need to manage if you're syncing.
Not perfect. But realistic.
The script I still keep around
For those of you who want to automate Chrome's native history cleanup despite my warnings, here's a slightly safer approach than raw SQLite editing. Use Chrome's DevTools protocol via Puppeteer or Playwright:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto('chrome://history');
// Search for domain
// Select visible entries
// Click delete
// Paginate and repeat
})();I'm being deliberately vague here because the selectors change with Chrome versions and this is fragile by nature. But it works when it works. The advantage over SQLite is that Chrome handles its own database integrity. The disadvantage is it's slow (you're literally automating the same clicking you'd do by hand) and it breaks every time Chrome redesigns the history page.
I used a version of this script for about four months. Then I set up TraceMind's excluded domains and stopped needing it for 90% of cases. Still fires it up occasionally for a really thorough cleanup.
What I wish Chrome would just build
A filter-and-delete-all button. That's it. Let me search for a domain in chrome://history, then click "Delete all matching results." Not 150 at a time. All of them. This is such a basic database operation that its absence feels intentional.
Until that happens (don't hold your breath), the combination that works for me is TraceMind for day-to-day history management with real bulk controls, and a grudging acceptance that Chrome's native history page was designed for casual users who visited 12 websites yesterday, not developers who hit 200+ unique URLs before lunch.
Your browsing data is yours. Managing it shouldn't require a lunchtime clicking session or a SQLite tutorial. It's 2025. We deserve better tools than Ctrl+H.
