Fix it now
- Drop the file
- Repair runs locally
- Download the result
Core Data's default and recommended persistent store type is NSSQLiteStoreType — a normal SQLite 3 database on disk, usually named <AppName>.sqlite inside the app's Library directory. When it won't open, the store coordinator throws an NSCocoaErrorDomain error and SQLite calls the file malformed, but a malformed database is rarely an empty one: the rows are still in their pages behind a broken index. Drop the .sqlite above — and add its -wal sidecar if you have it — and the tool reads the file in your browser, rebuilds the schema, and re-inserts every row it can still decode into a fresh, openable store. Nothing is installed and the database is never uploaded.
What Core Data actually writes to disk
Core Data is an object graph and persistence framework, not a database — the database is SQLite, and Core Data owns its layout. Open a Core Data store in any SQLite browser and you will not see friendly table names; you'll see a mangled schema the framework generates. Every entity becomes a table named Z + the uppercased entity name, so a Person entity is table ZPERSON. Each row carries three system columns: Z_PK (the integer primary key), Z_ENT (which entity/subclass the row is), and Z_OPT (an optimistic-locking version counter). Your attributes are prefixed too — firstName becomes ZFIRSTNAME — and a to-one relationship is stored as a foreign-key column holding the destination row's Z_PK.
Three bookkeeping tables sit alongside your data. Z_PRIMARYKEY holds one row per entity with columns Z_ENT, Z_NAME, Z_SUPER and Z_MAX — the highest primary key handed out so far, which Core Data increments to allocate the next insert. Z_METADATA holds Z_VERSION, Z_UUID and Z_PLIST, a binary-plist blob storing the store metadata including NSStoreModelVersionHashes and the store UUID. Z_MODELCACHE caches the compiled model. Dates are stored as a REAL count of seconds since the Cocoa reference date, 2001-01-01 00:00:00 UTC — not the Unix epoch — so a raw timestamp of 0 is New Year's Day 2001.
All of this lives in a standard SQLite container: a flat array of fixed-size pages (4096 bytes by default), the first 16 bytes of the file being the magic string SQLite format 3\000. Page 1 holds the file header and the sqlite_master schema; every Z-table is a b-tree whose interior pages point down to leaf pages that hold the actual records. Break one pointer and SQLite refuses the whole file — even though the leaf pages full of rows are untouched.
The -wal and -shm files: which one holds your data
Since iOS 7 (and OS X 10.9 Mavericks) Core Data opens its store in WAL mode — write-ahead logging — by default. That is why a single store is really up to three files on disk: <AppName>.sqlite, <AppName>.sqlite-wal, and <AppName>.sqlite-shm. Understanding the difference decides whether your newest data survives.
The -wal file is the write-ahead log. When your app commits a transaction, the changed pages are appended to the -wal as frames rather than written straight into the main .sqlite; only a periodic checkpoint folds them back into the database. The consequence: if the app crashed or was force-quit before a checkpoint, the freshest committed rows can live only in the -wal. Its layout is precise — a 32-byte header (magic 0x377f0682 or 0x377f0683, a format version, the page size, a checkpoint sequence, two salt values and a checksum) followed by frames, each a 24-byte frame header (page number, the database size in pages for a commit frame or 0 otherwise, the two salts, and two checksums) plus one page of data.
The -shm file is the shared-memory wal-index: a lookup structure SQLite uses to find pages inside the -wal quickly. It is entirely derived data — it holds none of your rows and SQLite rebuilds it automatically from the -wal. So the rule is simple: bring the -wal, ignore the -shm. Drop the .sqlite first, then add the -wal in the optional sidecar slot and its committed frames are overlaid before the scan, so recovery reflects the last committed transaction instead of the last checkpoint. Copying only the .sqlite off a device and leaving the -wal behind is the classic way to silently lose recent data — a trap for both crash recovery and forensic extraction.
The errors you see, and what row-salvage recovers
At the SQLite layer the store fails with one of two result codes. SQLITE_CORRUPT (code 11) prints "database disk image is malformed": the engine followed a pointer and found something that isn't a valid b-tree page — a zeroed page, a bad cell offset, a page count that disagrees with the file length. SQLITE_NOTADB (code 26) prints "file is not a database": the 16-byte header magic is wrong, which means either the header bytes were damaged (the pages behind them are often fine) or the file is genuinely something else or encrypted. Core Data wraps this and surfaces NSCocoaErrorDomain code 259 (NSFileReadCorruptFileError) — "The file couldn't be opened because it isn't in the correct format." — while the raw SQLite code is tucked into the error's userInfo under the NSSQLiteErrorDomain key, so a log line reading NSSQLiteErrorDomain=11 is your confirmation this is page-level corruption, not a schema-version mismatch.
Recovery does not try to fix the broken file in place. It reads out everything still decodable and writes it into a brand-new database — the same strategy as SQLite's own .recover command. It walks each Z-table's b-tree and decodes the leaf records; for pages the pointer chain can no longer reach, it falls back to a raw page sweep, catching self-describing records directly out of the file. Rows that can't be attributed to a known table land in a lost_and_found table rather than being thrown away, and Z_PRIMARYKEY's Z_MAX values are rebuilt from the recovered rows so the app can keep inserting without primary-key collisions. Because the sweep also reads freelist and un-vacuumed pages, rows your app deleted earlier can resurface — useful, but worth knowing.
External binary data lives outside the store
One honest limit is specific to Core Data. When a binary attribute (a photo, a PDF, audio) has "Allows External Storage" checked, Core Data decides per value whether to inline the bytes in the row or write them as a separate file when they exceed roughly 100 KB. Those external blobs are stored under a hidden support directory next to the store — .<storename>_SUPPORT/_EXTERNAL_DATA/ — with UUID filenames, and the database row keeps only a reference to that file, not the bytes.
So recovering the .sqlite alone brings back every scalar attribute and every inlined blob, but for externally-stored data it returns the reference, not the image. If you also have the _EXTERNAL_DATA folder from the same app container, those files pair back up by name; if that directory is gone, no database recovery can reconstruct bytes that were never inside the database. And because a Core Data store can hold an entire app's private state — every note, message, health record or account a user ever saved — this all runs in your browser: the file is read from disk, rebuilt in the tab, and you can watch the Network tab confirm 0 bytes leave your machine.
What this can and can't fix
Can fix
- "database disk image is malformed" (SQLITE_CORRUPT / code 11) from a damaged b-tree page or bad cell pointer, with the ZENTITY pages behind it intact
- Rows in each ZENTITY table that still decode — pulled out and re-inserted into a fresh, openable .sqlite store
- Uncheckpointed committed changes overlaid from a supplied -wal sidecar (the freshest rows after a crash or force-quit)
- A store whose 16-byte header or schema is damaged but whose leaf pages survive — the schema is rebuilt from the pages
- Rows the b-tree pointers can no longer reach, recovered by a raw page sweep into a lost_and_found table, with Z_PRIMARYKEY.Z_MAX rebuilt
Can't fix
- Rows physically overwritten or truncated off the end of the file — those bytes no longer exist on disk
- External binary blobs stored under .<storename>_SUPPORT/_EXTERNAL_DATA when you don't also have those files — the row holds only a reference
- Encrypted stores (NSFileProtectionComplete data copied while the device was locked, or SQLCipher) without the key — the pages read as ciphertext
- The -shm file on its own: it's a rebuildable wal-index, not your data — the -wal is the file that carries recent commits
- A guarantee that recovered data is complete or correct — data from a corrupt store is always suspect; verify it against a known-good source
If a repair fails, we tell you why (missing data versus broken structure), and you are never charged for a failed repair.