"Database disk image is malformed"

This is error code 11, SQLITE_CORRUPT: while following a b-tree, SQLite reached a page or pointer that no longer makes sense, so it refuses to trust the file. The rows themselves are almost always still on disk — it is the map to them that broke — and reaching them does not require sending your database anywhere.

Your file never leaves your device — repair runs in your browser. 0 bytes uploaded.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Fix it now

  1. Drop the file
  2. Repair runs locally
  3. Download the result

When SQLite — or anything built on it: a browser history store, a phone backup, a messaging app's local database, a desktop app's settings — reports "database disk image is malformed", it has hit SQLITE_CORRUPT (result code 11). It was walking the b-tree that indexes your tables and landed on a page whose type byte, cell pointers or record header contradict the format, so it stops rather than hand back garbage. Drop the .db, .sqlite or .sqlite3 file above and the tool reads it page by page in your browser, follows whatever b-tree structure survives, sweeps the rest with a pointer-free scan, and writes every decodable row into a brand-new, openable database — without the file ever leaving your machine.

What "malformed" means at the page level

A SQLite database is one file cut into fixed-size pages — a power of two between 512 and 65536 bytes, stored as a 16-bit value at offset 16 of the header (the on-disk value 1 is the spec's stand-in for 65536). Every file opens with the 16-byte magic string SQLite format 3\0, followed by a 100-byte header that records the page size, the text encoding at offset 56 (1 = UTF-8, 2 = UTF-16le, 3 = UTF-16be), the in-header page count at offset 28, and the freelist. Get the page size wrong and every page after the first lands at the wrong offset — which is one way a file goes "malformed" without a single row being touched.

Inside, the data lives in b-trees. Each page starts with a type byte: 0x0d for a table leaf (which holds actual rows), 0x05 for a table interior (which only holds pointers to child pages), and 0x0a/0x02 for the index equivalents. Page 1 is special — its b-tree header sits after the 100-byte database header, and it roots sqlite_master, the schema table whose rows are (type, name, tbl_name, rootpage, sql). To read one of your tables, SQLite looks up its rootpage there, then walks interior pages down to the leaves, reading each cell's varint payload length, varint rowid and record body.

Because a query traverses this tree, one bad link poisons everything beneath it. A zeroed interior page, a cell pointer that aims past the end of the page, a right-most pointer that loops back, or a record header whose serial types do not add up — any of these makes SQLite raise "database disk image is malformed" the moment it steps on the damage. The rows on the healthy leaf pages are untouched; SQLite simply cannot reach them through a broken index. PRAGMA integrity_check reports the same class of fault.

How a raw page-walk salvages your rows

The fix mirrors what SQLite's own .recover shell command does, but runs entirely in the browser. First the tool reads sqlite_master from page 1 to learn each table's name, columns (parsed from its CREATE TABLE SQL) and rootpage, then walks each b-tree from that root — the fast, exact path when the pointers are intact. When a walk hits a missing or invalid page it degrades to "incomplete" rather than throwing, keeping every row it collected up to the break.

Then comes the part that beats a plain re-open: a pointer-free sweep. The scanner ignores all pointers and treats every table-leaf page (type 0x0d) in the file as a bag of records, decoding each cell straight from its bytes — the varint header length, the serial-type array, then each value (serial type 0 is NULL, 8/9 are the literals 0 and 1, 7 is a float64, even/odd codes above 12 are BLOB/TEXT). Overflow chains for large values are followed page to page. This is exactly how rows survive a zeroed interior page, a freelisted page, or a dropped table: the leaf still holds the data even when nothing points at it. Rows whose column count uniquely matches a known table are slotted back into it; the truly un-attributable ones are written to a lost_and_found table with pgno, cellidx, nfield, rowid and c0…cN columns, so nothing decodable is thrown away.

Finally the tool builds a fresh database with sqlite3.wasm (loaded same-origin from /engines/sqlite/), issuing a permissive CREATE TABLE for each table — original column names and any INTEGER PRIMARY KEY preserved so the rowid alias round-trips, but no NOT NULL/UNIQUE/CHECK/foreign-key constraints that would reject a resurrected row — then INSERT OR IGNOREs every recovered row and exports the result as a clean .db. If a sibling -wal write-ahead-log file is provided, its committed frames are overlaid first, so recovery reflects the last committed transaction.

Where the data genuinely stops — and why nothing is uploaded

Recovery reaches what is present; it cannot invent what the disk no longer holds. If the file was truncated — a copy or sync that stopped early, leaving a size that is not an exact multiple of the page size — every page past the cut is physically gone, and you get the rows that were fully written before it. Cells that sit on intact pages but whose record headers are self-inconsistent (classic bit-rot) are dropped and counted rather than guessed. An encrypted database (SQLCipher or the SEE extension) has no plaintext magic — its first page is ciphertext — so without the key there is nothing to walk, and recovery is not attempted.

Two honesty notes SQLite itself insists on. Deleted rows can reappear: the pointer-free scan reads freelist and un-vacuumed pages, so records you thought were gone may resurface in the output. And recovered values reflect on-disk storage classes, not the declared column affinities — a column's apparent type can shift. SQLite's own guidance is blunt: data pulled from a corrupt database is always suspect. Verify it against a known-good source before you rely on it.

All of this runs in your browser tab — the scanner is dependency-free TypeScript and the writer is WASM, so there is no server round-trip. That matters when the database is a password vault, a chat history, health data or an app's private store: you can open the Network tab and confirm 0 bytes of the file ever leave your machine.

What this can and can't fix

Can fix

  • Databases that raise SQLITE_CORRUPT / "database disk image is malformed" from a damaged b-tree page or pointer
  • Rows on healthy table-leaf pages that a broken interior page or bad rootpage made unreachable
  • Rows on zeroed interior pages, freelisted pages or dropped tables, recovered by the pointer-free leaf scan
  • Large values that spilled onto overflow-page chains, reassembled during the walk
  • Uncheckpointed changes in a sibling -wal file, overlaid before scanning

Can't fix

  • Rows past the cut in a truncated file — those pages are physically gone
  • Cells whose record header is too corrupted to decode (bit-rot) — they are dropped, not guessed
  • Encrypted SQLCipher/SEE databases without the key (the pages are ciphertext)
  • The original UNIQUE/CHECK/foreign-key guarantees — recovery relaxes constraints so resurrected rows are not rejected
  • A file that is 0 bytes or has no SQLite magic and no decodable leaf pages

If a repair fails, we tell you why (missing data versus broken structure), and you are never charged for a failed repair.

FAQ

Does "database disk image is malformed" mean my data is lost?

Usually not. It means SQLite followed the b-tree onto a page or pointer that violates the format and stopped — error code 11, SQLITE_CORRUPT. The rows on the healthy leaf pages are still on disk; they are just unreachable through the broken index. Reading the leaf pages directly and rebuilding a fresh database recovers most of them.

How is this different from just re-opening the file or running PRAGMA integrity_check?

Re-opening fails the same way, and integrity_check only reports the damage. Recovery instead walks the b-tree where it can and, crucially, does a pointer-free scan of every table-leaf page — so it salvages rows on pages that nothing points at anymore, which a normal open can never reach.

What is the lost_and_found table in the recovered database?

It is where records that could not be matched to a known table are kept, rather than discarded. Each is stored with its source page number (pgno), cell index, field count and rowid, plus its raw column values as c0, c1 and so on — so you can inspect and re-home them by hand.

Why did deleted rows show up after recovery?

The pointer-free scan reads freelist and un-vacuumed pages, where SQLite leaves old records until the space is reused. Recovery favours completeness, so previously-deleted rows can resurface. Treat any recovered data as suspect and verify it against a known-good source.

Is my database uploaded to be recovered?

No. The file is read from your disk and rebuilt in your browser tab; the page scanner is plain TypeScript and the writer is sqlite3.wasm served from the same origin. You can watch the Network tab and confirm 0 bytes leave — which matters for chat histories, vaults, backups and health data.

Related: Repair a SQLite database · Repair an Excel workbook · "The file is damaged and could not be repaired" · Verify the zero-upload claim