signal-export: exporting Signal chats to Markdown and HTML
Export your Signal chats to markdown files with attachments
At a glance
- What is it?
- signal-export reads the local Signal Desktop database and writes each conversation to a Markdown file with its attachments, then converts the result to a styled HTML chat view. The hard part is no longer the export but the key, because recent versions of Signal Desktop protect the database encryption key, and the tool is honest about that.
- Who is it for?
- signal-export fits a Signal user who wants their own conversation history in a form they can read, search, and keep after they stop using the app, and who is comfortable running a Python tool locally against their own Signal installation. It does not fit a user who is still relying on the desktop app alone, because the export reflects the database as it is on disk at that moment, and it does not fit an Android user, where the project points at other tools.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 51 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Your own chat history, in a form you own
signal-export reads the chat database of the Signal Desktop app and writes it out as Markdown and HTML files with attachments. Each chat becomes an individual .md or .html file, and the attachments for that chat are stored in a separate folder next to it.
The output format is plain and readable. A conversation looks like a series of timestamped lines, with the sender name after the time in square brackets, so a group chat export reads as a plain text transcript. Images are attached inline with a Markdown image reference, so they render in place, and other attachments, meaning voice notes, videos, and documents, are included as links so a click takes you to the file.
At the end the Markdown is converted to HTML, and the HTML export is styled as a chat view with grouped messages, day dividers, reactions, and an image lightbox. It follows the light or dark preference of your browser or operating system by default, and a switcher in the top bar lets you pin light or dark, remembered per browser.
Two claims in the README are worth repeating because they frame the whole project. The first is that this seems to be the only way to get chat history out of Signal, which is a statement about the absence of a supported export path in the app rather than a marketing line. The second is that the project is adapted from signal-backup by mattsta, which the author suspects will be hard to get working now.
So this is a personal archive tool. It is for your own conversations, on your own machine, and the output is a set of text files and media you can read without the app.
The key is the hard part now
The README opens with a note that deserves attention before anything else, because it is the part that changes between Signal releases.
The latest versions of Signal Desktop protect the database encryption key, so decrypting involves some extra steps, and the note ends with a blunt instruction: good luck.
That single sentence explains why a tool like this has to be updated when Signal ships, and why the release cadence of the project matters. The Signal database is a SQLite database, and the dependency list in the project metadata confirms the approach: sqlcipher3-wheels for the encrypted database, pycryptodome for the cryptography, and the export side is built with typer for the command line, Markdown for the conversion, beautifulsoup4 for the HTML handling, emoji for the emoji handling in message bodies, and filetype for detecting attachment types.
The consequence for a user is that a script which worked last year may not work this year, and that is not a bug in the script but a change in the thing it reads. If the export fails, the first thing to check is which version of Signal Desktop you are running, and the second is the extra key steps for that version.
The project is explicit about the other requirement, which is procedural rather than technical. You should fully exit the Signal app before proceeding, otherwise you will likely encounter an I/O disk error, because the message database is made read-only while it is being accessed by the app. It is worth obeying exactly: a partially readable database produces a partial export that looks like a successful one.
Installing it with pip, pipx, nix, or from source
The installation requirements are modest. You need Python installed, and the package is published on PyPI under the name signal-export.
pip install signal-export
# ...if you have the "pipx" command available, you're probably better off installing with "pipx install signal-export"The pipx suggestion is the sensible one for a command line tool, since it keeps the tool and its dependencies in their own environment instead of the one your other scripts share.
Nix and NixOS users do not need to build anything, because the package is in nixpkgs.
nix-shell -I nixpkgs=channel:nixpkgs-unstable --packages signal-export --command 'sigexport ~/signal-chats'There is one wrinkle there. If you get an error about secret-tool not being found, you probably need to install libsecret-tools through your Linux package manager, and on NixOS you add libsecret to the same command.
nix-shell -I nixpkgs=channel:nixpkgs-unstable --packages signal-export libsecret --command 'sigexport ~/signal-chats'The secret-tool dependency is a sign of what the tool has to reach for. On Linux, the Signal key is held in the keyring, and libsecret is the interface to it, which is a better design than asking you to paste a key into a prompt.
Windows users have a separate document, INSTALLATION.md, for step by step instructions including how to enable WSL2. The reason for that is visible in the usage section, which offers a module invocation for Windows in addition to the console script.
For development, the project uses uv, with the clone and sync steps given directly.
git clone https://github.com/carderne/signal-export.git
cd signal-export
uv sync --all-groupsThe day to day commands are wrapped in poe tasks, with formatting, linting, type checking, and testing each named.
uv run poe fmt # format code with Ruff
uv run poe lint # lint code with Ruff
uv run poe check # typecheck with Pyright
uv run poe test # run tests with pytest
uv run sigexport # run the signal-export CLIRunning it, and the options that matter
The command itself takes the output directory as its argument.
sigexport ~/signal-chats
# or for Windows:
python -m sigexport C:\Temp\SignalExportThe full help is available from the command, and the four options worth knowing are about selecting chats, listing them, and controlling the HTML.
sigexport --helpListing the available chats and exiting without exporting anything is useful for a first run, because it confirms the tool can see the database before you start writing files.
sigexport --list-chatsExporting only selected chats takes a comma separated list of names.
sigexport --chats=Jim,Aya ~/signal-chatsAnd pagination in the HTML can be switched off, which matters when a conversation is long enough that the browser struggles with it.
sigexport --paginate=0 ~/signal-chatsThe tool finds the Signal configuration location itself on most systems, but there is a --source option for when it does not, and the README lists the default locations per operating system. Linux is ~/.config/Signal/, Linux Flatpak is ~/.var/app/org.signal.Signal/config/Signal, macOS is ~/Library/Application Support/Signal/, and Windows is ~/AppData/Roaming/Signal/. The directory you point at has to contain a folder called sql with db.sqlite inside it, which is the shape of the Signal data directory.
Merging a new export with an old one
The --old option merges a new export with a previous one, and the README is careful about what that means.
Nothing will be overwritten. The combined results go into whatever output directory you specified, and the previous export is left untouched. The README then adds a sentence that is unusually good advice for a tool that handles irreplaceable data: exercise is left to the reader to verify that all went well before deleting the previous one.
That is the right posture for an archive tool, and it is worth extending to the whole workflow. A first export is a snapshot of one database, and the database changes every time a message arrives, so an archive is only a good one if you know when it was taken.
The merge feature exists for the ordinary case where you export once, keep using Signal for a year, and export again, and want one directory rather than two. The alternative to merging is to keep dated exports side by side, which has the advantage that each one is provably the state at a moment in time.
For the people who cannot use the desktop app at all, the README points at other projects: signal-backup-decode, which might be easier if you use Android, signal2html, which is also Android only, and sigtop. That list is also the honest scope statement for this project, since the tool reads a desktop database and knows nothing about the mobile clients.
Maintenance, packaging, and the licence question
The project is not archived and the last push was on 2026-09-02, with releases v3.9.3 on that date, v3.9.2 on 2026-08-19, and v3.9.1 on 2026-08-09. The patch releases in quick succession are consistent with a tool that has to be adjusted when Signal changes, and the version number sitting in the low threes suggests an established project rather than a script that worked once.
The packaging is modern and worth a look if you want to extend it. The build backend is pdm-backend, the version is derived from the source control tag rather than written in the file, the package requires Python 3.10 or newer, and the console script is exposed as sigexport pointing at sigexport.main:cli. Linting is Ruff with a broad rule selection, including the bandit style security rules, and there is a Poe task file with the four standard tasks and an all task that runs them in sequence.
The licence needs one note. GitHub classifies the repository as having a custom licence that it cannot classify automatically, and the README points at the LICENSE file, while the project metadata in pyproject.toml declares the licence as the MIT License and the classifiers list it as an OSI approved MIT licence. The authoritative text is the LICENSE file in the repository, and it is worth reading before you redistribute anything derived from this tool.
The last thing to weigh is the nature of the data. An export of your Signal history is an archive of private conversations, including the media in them, and it is stored as ordinary files in an ordinary directory. That directory deserves the same care as the chat itself, because a Signal conversation is end to end encrypted in transit and in the app, and an export is neither.
Editorial conclusion
signal-export fits a Signal user who wants their own conversation history in a form they can read, search, and keep after they stop using the app, and who is comfortable running a Python tool locally against their own Signal installation. It does not fit a user who is still relying on the desktop app alone, because the export reflects the database as it is on disk at that moment, and it does not fit an Android user, where the project points at other tools. Verify first that you have fully exited Signal before running it, because the database is made read-only while the app is running, and that you have the correct database key for the version of Signal you have installed, since the extra decryption steps the tool needs have changed with recent releases.
Frequently asked questions
How do I export my Signal chats with signal-export?
Install it with pip install signal-export or pipx install signal-export, fully exit the Signal desktop app so the database is not read-only, then run sigexport with an output directory, for example sigexport ~/signal-chats. Each chat is written as a Markdown file with its attachments, and an HTML version is produced as well.
Why does the Signal export fail with an I/O disk error?
Because the Signal app is still running. The message database is made read-only while the app is accessing it, so you have to fully exit Signal before running the export, otherwise you are likely to hit an I/O disk error and a partial export.
What is the hardest part of exporting Signal chats?
The database encryption key. The README notes that the latest versions of Signal Desktop protect it, so decrypting involves some extra steps, which is why the tool needs updating when Signal changes. The key itself is held in the system keyring on Linux, which is why the libsecret tooling is needed there.
Can I merge a new signal-export run with an older export?
Yes, with the --old option pointing at the previously exported directory. Nothing is overwritten: the combined results go into the output directory you specified and the previous export is left untouched, so you are told to verify the result before deleting the old one.
Does signal-export work for Android Signal accounts?
No, it reads the Signal Desktop app database, whose location it looks for on Linux, Flatpak, macOS, and Windows. The README points to other projects for Android users, listing signal-backup-decode, signal2html, and sigtop.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/carderne-signal-export)Was this useful?