A write-ahead log viewer is a tool for inspecting SQLite -wal files so an examiner can review earlier versions of database pages, identify transaction boundaries, and recover records that no longer appear in the live database.
The live database shows the current state. The write-ahead log holds the states that came before it, and often holds them for a long time.
In WAL mode the database file is deliberately not kept current. When a page changes, the complete new version of that page is appended to the -wal file as a frame. The database file waits for a checkpoint to catch up.
The purpose is to stop readers and writers blocking each other. The forensic consequence is that a page changed ten times exists on disk as ten complete versions, in the order they were written.
That is why opening the live database and reading the tables answers a narrower question than most people assume. The tables show the current state. The WAL holds the states that produced it.
It is where you go to answer questions the live database cannot:
A -wal file is a 32 byte header followed by frames. Each frame is a 24 byte header plus one complete page image.
| Offset | Size | Meaning |
|---|---|---|
0 | 4 | Page number |
4 | 4 | Database size in pages, if this frame commits. Otherwise zero. |
8 | 4 | Salt-1, copied from the file header |
12 | 4 | Salt-2, copied from the file header |
16 | 8 | Two running checksums |
The field at offset 4 is the one people skip. It reads zero on almost every frame. When it holds a value, that frame ends a transaction, and the value is the size of the database afterwards. It is the only structural marker of a transaction boundary in the file, which makes it the only way to tell which frames belong together.
frame 1 00 00 00 04 00 00 00 00 frame 2 00 00 00 07 00 00 00 00 frame 3 00 00 00 02 00 00 00 0C frame 4 00 00 00 04 00 00 00 00 frame 5 00 00 00 09 00 00 00 0C frame 6 00 00 00 04 00 00 00 00 frame 7 00 00 00 07 00 00 00 00 frame 8 00 00 00 03 00 00 00 00 frame 9 00 00 00 02 00 00 00 0C
Frames 1 to 3 are one transaction, because frame 3 commits. Frames 4 and 5 are a second. Frames 6 to 9 are a third. Three user actions, nine frames.
Now look at page 4. It appears in frames 1, 4 and 6. That is three versions of the same page, and only the last one is current. The other two are history, and they are readable.
A checkpoint copies frames from the WAL back into the database file. After that the WAL can restart, meaning the next write begins again at frame 1.
The salt values at offsets 8 and 12 exist for integrity. If a reader is working through the file and it restarts underneath, the salts stop matching and the reader knows to stop rather than stitch together pages that never coexisted.
When the WAL restarts, both salts change. Frames further down the file keep the previous pair, because nothing goes back to clear them, and the file is usually not shortened. SQLite stops reading at the first mismatch. Everything past that point is invisible to the engine.
header offset 16 00 00 00 05 salt-1
header offset 20 9A 3F C1 07 salt-2 frame 41 00 00 00 04 00 00 00 00 00 00 00 04 1E 77 B2 D9 Salt-1 is 4, not 5. That frame belongs to the previous generation of this WAL. It is a complete page image from before the last checkpoint, sitting on disk in full, and it is one of the better sources of historical state in the whole format.
The frame checksums are cumulative, each computed over the running value from the frame before. The point where the chain breaks is another way to find where the current generation ends and the leftovers begin.
Because there is no timestamp anywhere in the format. Not in the file header, not in any frame header. Any time you have dated something out of a WAL, that came from a value inside a page rather than from the log itself.
Frame order is the order SQLite flushed pages, which is related to the order of events and is not the same thing. Sending one message can write the table leaf, an index page, sqlite_sequence and a freelist page. Four frames, one action. Meanwhile a background task the user never triggered writes frames in between.
Present frame order to a court as the order of events and you are presenting the order in which pages happened to be flushed. What the file gives you reliably is states, not moments.
A sequence of states. Page 4 looked like this, then like this, then like this. What happened between them, and when, has to come from inside the pages.
That a page held certain content at some point before it held its current content.
When that happened, or that adjacent frames belong to the same user action.
By never opening the original with a read-write connection.
A standard SQLite connection can checkpoint the WAL when it closes, merging its contents into the database file, and depending on journal mode it can delete the -wal and -shm files entirely. Everything described on this page can disappear in the moment a handle closes, including from a tool that only intended to look.
Copy the database, the -wal and the -shm together, and work on the copy. If you must touch the original, open it read only.
Nothing warns you when it goes wrong. The file is simply smaller than it was, and the frames you needed are gone. It is worth building the habit before you need it.
The -shm file is the index SQLite builds so readers can find the newest frame for a page without scanning the whole file. It is derived rather than primary and is rebuilt when needed, so treat it as a signal about state rather than as evidence in itself.
See each version of a page in write order, not only the current state.
Group frames by commit, so you know which writes belonged together.
Reach the page images from earlier generations that SQLite itself will not read.
Interpret a recovered page against the schema rather than as loose bytes.
Move from a recovered record to the offset it came from without leaving the case.
Open the evidence without the risk of checkpointing it on close.
Most database browsers were not built for this. They open the database, silently merge the WAL, and show you the result. That is correct behaviour for a developer tool and the wrong behaviour for evidence.
A forensic WAL viewer has to do the opposite: leave the source untouched, expose the frames rather than the merged result, and keep each recovered record connected to the frame and offset it came from.
Firefly treats WAL analysis as part of the case rather than as a separate artifact. Records recovered from WAL frames appear alongside live records and alongside records from rollback journals, freelist pages and freeblocks, each labelled with the mechanism that surfaced it.
That labelling is the point. A record recovered from a freelist page arrives whole. A record recovered from a freeblock has lost its rowid. A record recovered from an earlier WAL frame is a previous state rather than a deletion. Knowing which one you are holding changes what you can say about it.
Evidence handling is read-only throughout, with SHA-256 evidence-set validation, and everything runs locally. Where the data supports it, a finding keeps its source location and that context carries into an interactive report.
Firefly is not an extraction tool. It starts where decoded output stops and assumes you keep the platform you already use. SQLite Visualizer is now part of Firefly.
The broader framework is on SQLite Forensic Analysis. On validating what a viewer shows you, see When a Viewer Is Not Enough.
Everything on this page is format knowledge rather than tool knowledge. It transfers to whatever platform is on your desk, and it is taught in two places depending on how much of it you want.
The write-ahead log on its own, taught at the level this page describes. Built for examiners who need pre-checkpoint recovery now rather than a full course.
The whole format, with WAL and SHM as one part of it. Guided and self-paced, with real training databases you download and work on in your own tools.
No coding required. Written for investigators rather than developers. Three months of access, course related email support and a one hour instructor session.
Certified SQLite Forensics also runs live online and on-site by arrangement. See the formats.
Last updated 6 September 2026
Continue through related SQLite investigation workflows and connect this topic back to the main forensic tool.