Fix it now
- Drop the file
- Repair runs locally
- Download the result
The History file is a standard SQLite database (16-byte magic SQLite format 3\0), so when a tool reports database disk image is malformed on it, or Chrome loses your history after a crash, it is the same class of failure any SQLite file can hit: one damaged page or pointer, not wholesale data loss. Drop the History file above and the scanner opens it in your browser, walks the pages directly, rebuilds the schema, and re-inserts every row it can still decode into a fresh, openable database — the file never leaves your machine.
Chrome's History is a SQLite database with no file extension
Your history is not stored in some proprietary format — it is a SQLite database sitting in Chrome's profile directory: on Windows %LOCALAPPDATA%\Google\Chrome\User Data\Default\History, on macOS ~/Library/Application Support/Google/Chrome/Default/History, and on Linux ~/.config/google-chrome/Default/History (Chromium uses ~/.config/chromium/…, Edge …/Microsoft/Edge/…, Brave …/BraveSoftware/Brave-Browser/…). The file is called History with no extension, which is why people don't realise they can open it in any SQLite tool — rename a copy to History.db and DB Browser for SQLite will read it straight away.
Inside are the tables that make up your browsing record. urls holds one row per page you've visited — its columns are id, url, title, visit_count, typed_count, last_visit_time and hidden. visits is the timeline: each row has a url foreign key back into urls.id, a visit_time, a from_visit that points at the previous visit (so back/forward chains reconstruct), and a transition bitmask whose low byte is the core type (LINK = 0, TYPED = 1, AUTO_BOOKMARK = 2, RELOAD = 8). Alongside them sit keyword_search_terms, downloads and downloads_url_chains, segments/segment_usage (which drive the New Tab shortcuts), and a small meta table with version and last_compatible_version rows that tell Chrome which schema it's looking at.
One detail trips up everyone who exports history: the timestamps are not Unix time. last_visit_time and visit_time are stored as microseconds since 1601-01-01 00:00:00 UTC (the Chrome/WebKit epoch). To read one as a normal date you divide by 1,000,000 and subtract 11,644,473,600 seconds. Recovery preserves the raw integers exactly as they sit on disk, so the dates survive the rebuild — you just convert them afterwards.
How the History file goes malformed: pages and pointers
A SQLite database is a flat array of fixed-size pages — Chrome uses the 4096-byte default. Page 1 carries the header and the schema; every table, including urls and visits, is a b-tree whose interior pages point down to leaf pages that hold your actual rows. Chrome runs the History database in WAL (write-ahead logging) mode, so next to it you'll normally see sibling files named History-wal and History-shm; older builds and some profiles instead leave a rollback journal called History-journal.
That sidecar machinery is exactly where damage sneaks in. If the machine loses power, the disk fills, or Chrome is killed mid-checkpoint, a WAL frame can be written only halfway back into the main file — a single interior page gets an inconsistent cell pointer or a page count that disagrees with the file size. SQLite follows that pointer, finds something that isn't a valid b-tree page, and stops with Error: database disk image is malformed (11) — result code SQLITE_CORRUPT. Run PRAGMA integrity_check and you'll see the specifics: lines like *** in database main ***, Page 214: btreeInitPage() returns error code 11, row 1803 missing from index urls_url_index, or wrong # of entries in index visits_url_index.
The important part: the engine halts at the first inconsistency, but the pages it never reached are usually perfect, and your rows are still sitting untouched in their leaf pages. It's the same story as a video's missing moov index or a ZIP's broken central directory — the map broke, not the data. That's why a page-level rebuild recovers so much where opening the file normally recovers nothing.
What recovery pulls back
You don't fix a malformed History file in place — you read out everything still decodable and write it into a brand-new database, exactly the way SQLite's own .recover command works. The scanner reads every page locally, reconstructs the schema from the surviving definitions, then walks each table's b-tree and decodes the self-describing records in its leaf pages. Rows the pointer chain can no longer reach are found by a raw page sweep, and anything that decodes but matches no known table is written to a lost_and_found table rather than thrown away.
In practice that brings back the substance of your history: the urls table (addresses, page titles, visit_count, last_visit_time), the visits timeline with its transition types and from_visit chains, and — where their pages are intact — keyword_search_terms, downloads, and the segments data. If you still have the History-wal sidecar, add it in the optional slot after you drop the main file: its committed frames are overlaid before the scan, so the rebuild reflects the last committed transaction instead of the last checkpoint — often the newest few hours of browsing. The output is a fresh, openable SQLite file you download; rename it back to History, drop it into the profile folder while Chrome is closed, and your history reappears, or just query it in any SQLite viewer.
Where it genuinely stops
Recovery returns what is physically present; it cannot invent what was overwritten. Rows that were truncated off the end of the file, or whose pages were reused after a delete, are gone — no tool can bring those back. The single biggest loss with Chrome specifically is time: when Chrome opens a History file it considers malformed, it frequently deletes it and creates a fresh, empty one on the spot, so the corrupt original is overwritten in place. If that already happened, close Chrome immediately and recover from a backup or a copy of the old History/History-wal — the empty file that replaced it has nothing in it to find.
Two smaller caveats, stated plainly. Recovered values come back as their on-disk storage class, so a column's declared affinity may not be reapplied — timestamps are still the correct microsecond integers, you just convert them yourself. And recovered data from a corrupt database is always suspect: SQLite's own documentation says so, and because a raw scan also reads freelist pages, a previously-deleted URL can occasionally resurface. The recovery report says table by table what came out and what was pulled by the raw scan. None of this needs a server: the History file — which is a map of everywhere you've been — is read from your disk and rebuilt in your tab, and you can watch the Network tab to confirm 0 bytes leave.
What this can and can't fix
Can fix
- "database disk image is malformed (11)" on the Chrome/Chromium History file from a damaged page or b-tree pointer
- The urls table — addresses, page titles, visit_count, typed_count and last_visit_time — from surviving leaf pages
- The visits timeline: visit_time, transition types and from_visit back/forward chains
- keyword_search_terms, downloads and segments data where their pages are intact
- Uncheckpointed browsing from a supplied History-wal sidecar (overlaid before the scan)
- Corrupt header fields (bad page size or page count) with intact pages behind them
Can't fix
- History that Chrome already deleted and replaced with a fresh empty file — the old rows were overwritten in place
- Rows physically truncated off the end of the file, or on pages reused after a delete — those bytes no longer exist
- A guarantee of correctness — recovered data is always suspect; deleted URLs can resurface from freelist pages, so verify it
- Exact column affinities: values come back as their on-disk storage class (timestamps stay correct, you just re-convert them)
- A 0-byte History file — that's a storage-device data-recovery problem first, before any SQLite rebuild
If a repair fails, we tell you why (missing data versus broken structure), and you are never charged for a failed repair.