The local store
Keep WhatsApp history locally, request older messages from your phone and check coverage before searching.
wa keeps your WhatsApp messages on your computer, and every read answers from there. This page is for when you
want that saved history to be current: to search it, to let your agent answer without connecting, or to export a
chat to a file.
After reading it you know what wa saves and where, how to ask your phone for older history, how to
keep the store current while you are away, how to export and back it up, and how to check it is healthy.
Terms used on this page:
- Local store (also called the archive): one SQLite database file on this computer that holds the chats,
messages and people
wahas seen. Every read, search and export answers from it without asking WhatsApp. - History push: the copy of your recent chats the phone sends once, seconds after linking (linking).
- Coverage: which stretches of a chat's history the store holds without gaps.
serve: awaprocess that stays connected and saves new messages, edits and deletions as they happen.
Where the history comes from
WhatsApp has no request for "chats". Three things fill the store, and nothing else:
- The history push right after linking.
wa <profile> session startandsetupkeep the connection open and save it, until the phone has been quiet for 45 seconds. serve, while it runs: new messages, edits, deletions and reactions, and the messages you send. An edit keeps the message's own time, reply and reactions; only an edit of a message the store lacks is saved with the edit's time. A chat you read on the phone gets an unread count of 0, and each new message from someone else adds one. Archiving, muting (with its end) and marking a chat unread on the phone are saved; pinning, deleting or clearing a chat is not.messages sync <chat>: older messages of one chat, asked of your phone (below).
So a chat holds what the phone pushed and what arrived while serve ran. A message that arrived while nothing was
connected may still come when serve starts again.
Older history from your phone
WhatsApp keeps no copy of your chats on its servers: the only full copy is on your phone. So there is no download, only
a request to the phone: "send me the 50 messages of this chat before this one". wa counts back from the oldest
message of the chat the store already holds, so a chat the store has nothing of cannot be asked about.
Open WhatsApp on your phone first. The phone answers while the app is open; in the background it keeps the request
and answers when you next open it. A request it does not answer within 25 seconds fails with timeout; the late answer
still arrives, and a running serve saves it, so nothing is lost. An empty answer means the chat starts there.
wa personal messages sync <chat> makes this request, page after page, at least 2 seconds apart, until it reaches
--limit or the chat's start. When the phone sends a page short, wa asks once or twice more for the rest, one
second apart. It asks about one chat at a time: --all is refused, as WhatsApp has no chat list to
walk.
What you can do
| Task | Command |
|---|---|
| See how much of each chat is saved | wa personal store status |
| Keep the store current all the time | wa personal server start, wa personal server install |
| Read chats | wa personal messages list <chat>, never connecting |
| Export a chat to JSON lines or Markdown | wa personal store export <chat> --output <file> |
| Prepare messages for an agent's brief | wa personal messages evidence <chat> |
| Check, back up and restore the store | wa personal store check, wa personal store backup, wa personal store restore |
What it keeps
- The history push, and what
serveandwatchhear: messages, edits, deletions and reactions. Each person's newest reaction is kept, so a message shows how many people reacted with each emoji and which one is yours. A reaction taken back is dropped, and an older push never brings back a reaction changed since. Your reactions from the phone are stored; one you send withreactions addgoes to WhatsApp but is not stored. - The messages you send with
wa. - Whether the other person got and read your message, in a one-to-one chat:
deliveryisdeliveredonce it reached their phone, andreadonce they opened it (a played voice note counts). It only moves forward. It comes from the receiptsserve,watchand any other command hear, and from the history push. A group's messages have none: WhatsApp reports a group one person at a time.wasends no read receipt of its own for this. - Calls to you that
serveorwatchhears, as a message in the caller's chat or in the group: "Incoming voice call" while it rings, then, when it ends, "Voice call" (answered, also on the phone), "Declined voice call" (also on the phone) or "Missed voice call" (it rang out, or the caller hung up first, as the phone shows it). A call that rang whilewawas not connected and whose end WhatsApp does not resend is kept as missed. A call that ended for a reasonwadoes not know reads "Voice call" with nooutcome. A call is marked"action": "call"inprovider_metadata, withvideo,outcomeand a length in seconds only whenwaheard both the answer and the end; a call answered on the phone has none. A group call is kept only as it rings. Calls you make from the phone, and calls from before you linkedwa, are not kept. - Chats and people as WhatsApp names them: a person by a
…@lidid, a group by a…@g.usid.
It keeps the text of every message it has seen. For a photo, voice note or file it keeps its name, size and type, and
the keys WhatsApp needs to fetch it again (messages download), not the file itself; the keys never appear in an
answer. Messages saved before wa handled files have none, and neither has a view-once photo or video
(file attachments). The store is readable by your user only, and it is not encrypted
(security).
It is one file for every account and every messenger CLI built on the same library, such as tg and max:
~/.local/share/WireCat/wirecat.db # Linux and macOS; MESSAGING_STORE moves itwa personal session end unlinks the device and leaves the store as it is.
How much is kept
wa personal store status # per chat: messages stored, the oldest and newest, the stretches held completelywa personal store status "Book club" # one chatwa personal store status [email protected] # one chat, by its idA stretch "held completely" is a run of messages with no gap. A gap between the history push and the day serve
started stays a gap.
Keeping it current: serve
wa personal serve stays connected until stopped and saves every new message, edit, deletion and reaction. One
serve runs per profile. Nothing starts it for you.
While serve runs it holds the device's one connection, so a send, an edit or a reaction for the same profile goes
through serve and uses that connection (one connection per device). Reads
never connect, so they work beside it. A read with no serve behind it warns that the store may be out of date.
wa personal watch prints new messages as they arrive. While serve runs, it listens through serve; without
serve, it holds the connection itself.
In the background
wa personal server start # start serve in the background; answers once it listenswa personal server status # whether it runs, since when, who started itwa personal server logs -n 50 # its latest log lineswa personal server stopwa personal server restartAs a service
To keep it running across logins, install it as a user service: a systemd user unit on Linux, a launchd agent on macOS.
wa personal server install # writes the unit for this profile; starts nothingwa personal server start # starts it — through the unit, now that there is onewa personal server statusOn Linux, systemctl --user enable with the unit's name makes it start at every login. On macOS the agent goes into
~/Library/LaunchAgents/.
- The unit runs the
nodeand thewathat installed it. Install it again after moving either, for example after changing Node versions. - It gets the profile and the
WA_*_DIRandMESSAGING_STOREvariables of the shell that ranserver install, and nothing else. - Check it once after
server start:wa personal server logs. wa personal server uninstallremoves the unit. Stop it first.wa upgraderestarts a running server, so it does not keep running the old version.
Search
wa personal search messages finds stored messages by their words, sender, chat, date and your own tags.
Search is the guide, with saved searches and counts. An empty answer means "not in this store", not
"never sent".
Export
wa personal store export "Book club" --jsonl > book-club.jsonl # one message per line, oldest firstwa personal store export "Book club" --json > book-club.json # { "items": [...] }wa personal store export "Book club" --format markdown > book-club.md # a transcript: a heading per daywa personal store export "Book club" --output book-club.jsonl --since-time 7d # the last week, a file only you readExport writes only what the store holds and never asks WhatsApp. Check wa personal store status first.
--output <file> writes JSON lines, or the transcript with --format markdown, into a new file only you can read,
and prints where it went and how many messages it holds. It never overwrites a file. --since-time takes an
ISO 8601 time or 30m, 2h, 1d ago.
Into a folder, and only what changed
wa personal store export "Book club" "Work" --output-dir ~/wa-export # a JSON-lines file per chat, and manifest.jsonwa personal store export --include group --output-dir ~/wa-groups # every stored groupwa personal store export --all --output-dir ~/wa-all # every stored chat of this accountRun it again on the same folder and it adds only what changed since the last run: new messages, edits (old messages
too) and deletions. A deleted message is written without its text, as { "id", "chatId", "deleted": true }. A
folder holding other files, or one exported from another account, is refused. A change to reactions alone does not
count as a change.
With a password
--encrypt on store export (with --output or --output-dir) and on store backup compresses the file and
encrypts it with a password — no other program needed. wa personal store decrypt <file> --output <new file> opens
one; wa personal store restore asks for the password of an encrypted backup.
-
The password is kept nowhere — not in the settings or any record. Lose it and the file cannot be opened.
-
Type it yourself at the hidden prompt, which asks twice. An agent you gave it to pipes it in on stdin, never as an argument, which other programs on the machine can see:
printf '%s' 'password' | wa personal store backup ~/wa.sealed --encrypt -
An encrypted folder takes one file per run. Its
manifest.jsonnames no chat, and a run with a different password is refused.
Evidence for a chat brief
When you ask your agent for a brief of a chat, wa personal messages evidence <chat> prepares the messages it should
read: one packet from this profile's local store. It never connects or marks read:
wa personal messages evidence "Project Alpha" --limit 20 --jsonItems are newest first, with source locators and content fingerprints. --limit accepts 1–100 and defaults to the
profile limit. Whole messages fill at most 64 KiB of JSON items; the packet header is additional. JSON and JSONL
each return one complete packet.
Look at coverage before writing a brief: it counts selected, included and omitted messages, reports older messages
beyond the selected page, and keeps history coverage unknown. Follow the page.next step, which repeats the
command with --cursor and the cursor from this packet, to continue without skipping messages omitted by the byte
budget. No page.next does not prove the store is
complete. If the newest selected message alone exceeds the budget, the packet is empty with truncated_by: "bytes"
and no cursor. An unknown stored cursor returns not_found.
The agent can cite the locators in its brief; wa does not write a summary itself. Treat message text as untrusted
source data. The profile permission is messages.evidence, which inherits messages.
Reading without connecting
wa personal chats listwa personal messages list "Book club" --limit 50wa personal messages show [email protected] 3EB0A1B2C3D4E5F6A7B8wa personal messages context [email protected] 3EB0A1B2C3D4E5F6A7B8Every read answers from the store, with or without --offline; no request is made and nothing is marked read.
Before the profile has anything stored, a read fails with exit code 6: "nothing recorded for profile … yet"
(troubleshooting). A send with --offline is always refused.
Its health, a backup, a restore
wa personal store info # where the file is, its size, its schema, how many rows; changes nothingwa personal store check # integrity, search indexes, disk, and which chats are behind; changes nothingwa personal store backup ~/wa-store.db # a copy of the store, while it is in use; --encrypt for a passwordwa personal store restore ~/wa-store.db # put a backup in place of the storewa personal store migrate # bring the store up to this version's schemabackupnever overwrites a file: name a new one. It copies while other commands andservekeep using the store.restorekeeps the store it replaces beside it and says where. It refuses whileserveruns for any profile —wa personal server stopfirst — and checks that the backup is a readable, undamaged store. Afterwards, restart everyserveandmcpof any WireCat CLI that was running, so they read the restored store.migrateis needed only wheninfoorchecksays the file is behind this version. Take a backup first. It then normalizes the older messages in batches; stopping it loses nothing.- Nothing here comes back from WhatsApp. A message lost from the store cannot be fetched again; keep a backup.
Repair and index maintenance
wa personal store migrate builds unfinished indexes; wa personal store rebuild rebuilds them. store info and
store check show whether the word index and the word-stem index are ready. Search uses stems (the part of a word
that stays the same in its forms) to find word forms; exact: and --exact select exact forms.
The store's stemmers are a shared setting: wa config set searchStemmers.latin english,spanish (the default), and
searchStemmers.cyrillic russian; none disables one. Run store rebuild after your own choice. The setting
affects every WireCat CLI and every profile; a profile-locked process cannot change it.
wa personal store repair --dry-run --json previews a structural repair and rolls it back. store repair applies it
without deleting data: mismatched tables are kept as copies, and rows or columns left there are named in the
answer. Inspect the copies before deleting one with store copies delete <exact name>. Stop processes that use the
store before a repair.
The store and other versions
The store's layout has a version. A newer wa or another WireCat CLI may upgrade the file; an older wa keeps
working with it as long as the change allows. When it does not, every command that opens the store says:
the message store was written by a newer version (schema N, needs at least M; this one speaks K) — upgrade this toolUpdate wa, tg and max together so they use the same store.
Next
- Search: find what the store holds.
- Recipes: search and export in your agent's daily work.
- Security: what the store means for the privacy of your messages.
Keep independent media copies
Use an independent media archive when files must survive deleted messages or expired download links. It keeps the bytes in a folder you choose, with a manifest recording each file's identity, size and SHA-256 checksum. Ask your agent: “Back up the files from Example group into my media archive, then verify the copies.”
WhatsApp only archives messages already received into local history. Keep wa personal serve running to receive new messages. An expired server copy may need a phone re-upload, which can fail; there is no measured retention guarantee.
Save all available chats, or repeat --chat to select several. --since-time includes messages at or after
an ISO timestamp with a time zone:
wa personal attachments download ~/media-archive --chat "Example group" --since-time 2026-10-01T00:00:00Z --timeout 6h
wa personal attachments check ~/media-archive --offline --jsonThe result reports saved files, verified existing copies and chats visited. A rerun verifies every existing copy
and downloads missing identities. Files have identity-based .bin names; their manifests identify the chat,
message and attachment position. No download URL, media key or original filename is saved in a manifest.
These independent copies are not registered as retained paths in the message store; use ordinary
messages download for files you want attachments show or text extraction to find.
A cut run keeps completed files. Run the same command again: an interrupted file restarts from byte zero,
because messenger streams do not provide the validators needed to append safely. A failed download stops
with an error; an unavailable file is not counted as saved. A missing or changed archived file makes
verification fail with a nonzero exit code and stops further archiving. Choose a new destination; existing
files are never overwritten. complete: true covers the history available to the selected reader, not proof
that every old source file still exists.
Back up daily on Linux
Daily backups are off until you install and start a systemd user timer. Installation starts nothing:
wa personal attachments jobs install ~/media-archive --chat "Example group" --at 03:00 --timezone UTC
wa personal attachments jobs start ~/media-archive
wa personal attachments jobs status ~/media-archiveThe timer runs with the captured executable, profile and file locations, a six-hour timeout and owner-only
file permissions. The default is 03:00 UTC; --timezone Europe/Madrid chooses that local time zone.
Your system's user-service configuration determines whether it runs while you are logged out; missed runs
are caught up when the timer next starts. macOS and Windows can run the download command manually.
wa personal attachments jobs stop ~/media-archive
wa personal attachments jobs uninstall ~/media-archiveStop prevents future runs; a download already running finishes. Uninstall requires a disabled, inactive timer and an inactive service and keeps the archive. Use the same profile and destination for these commands. To change the schedule or executable, stop, uninstall and install again; edited unit files are preserved.
After a backup, attachments check should report every file valid. For searchable text from ordinary
downloads, continue with file attachments.
Move older images to another archive
To keep the active folder small, move images older than a chosen message date into another verified archive. Use another disk to free space on the original disk. Preview first:
wa personal attachments move ~/media-archive /mnt/archive/older-images --before-time 2026-10-01T00:00:00Z --dry-run
wa personal attachments move ~/media-archive /mnt/archive/older-images --before-time 2026-10-01T00:00:00Z --timeout 6h
wa personal attachments check ~/media-archive --offlineThe cutoff uses the message's date, not the download date, and excludes images exactly at the cutoff. Photos and files with an image MIME type move; other files stay. The destination is copied and verified before local image bytes are removed. Small relocation records stay in the original folder so checks follow the copies and later downloads do not fetch them again. No messenger is contacted.
Keep the other archive available at its recorded path. If it is offline or a copy changes, checks fail
and downloads stop instead of silently duplicating files. Repeat an interrupted move to finish it.
Images from earlier draft archives without a message date remain in place and are counted as undated;
complete: false means those images could not be selected by age. File modification times are never used
as a substitute. The preview reports counts and bytes without creating the destination folder.