Markwell — back up and export your Kobo highlights and notes to Markdown, JSON, CSV, Anki TSV, and HTML.
Project description
Markwell
Mark well what you read. Back up and export your Kobo highlights into a corpus you own.
Safely back up, read, and export your Kobo highlights and notes — readable pages in your browser, plus Markdown, JSON, CSV, Anki flashcards, and a printable HTML library. Everything stays on your computer: no account, no cloud service, no network connections. Cross-platform, zero dependencies (Python standard library only).
Get Markwell
Download the app for your computer from the latest release:
- macOS —
Markwell-macOS.zip - Windows —
Markwell-Windows.zip
Unzip it and open the app: Markwell opens in your web browser, running entirely on your own computer.
If your computer hesitates at first launch — the download is unsigned
Markwell is free software and the downloads carry no code-signing certificate, so your operating system asks for one extra confirmation the first time:
- macOS — right-click (or Control-click) Markwell and choose Open, then click Open in the dialog. macOS remembers your answer, so this is only needed once.
- Windows — when the SmartScreen window appears, click More info, then Run anyway.
If you'd rather not run an unsigned binary, install the Python package below instead — the same app, with no bundled executable.
Prefer the command line? Markwell is also a Python package (Python 3.9+):
pipx install markwell # or: pip install markwell
markwell # the command-line tool
markwell-gui # the same app the desktop download runs
Why
Your highlights and notes are the irreplaceable part of your reading. Markwell:
- Never writes to your device. It only ever reads the Kobo database, copying the file to a local snapshot. Nothing — not even SQLite housekeeping — touches the device.
- Keeps every snapshot as immutable history. Each run saves a timestamped
KoboReader-<stamp>.sqlitethat is never overwritten, so you accumulate a full history of your reading database. - Gives you portable output. Human-readable Markdown, a documented JSON file, CSV for spreadsheets, Anki flashcards, and a self-contained HTML library — feed them into Obsidian, Anki, Excel, Readwise, or your own scripts.
The exports always mirror the latest snapshot only — they are a fresh projection of one database, not a growing archive. So if you delete a highlight on the device, it disappears from the next export. To recover it, re-export from a dated snapshot:
markwell --db backups/KoboReader-<stamp>.sqlite
The app (no terminal needed)
The desktop downloads above open straight into the app; from a terminal, it is:
markwell-gui # or: python3 -m markwell.gui
It opens in your web browser and lets you, in plain language:
- Back up — one button snapshots your Kobo and turns your highlights into readable pages, with live progress and a clear result.
- Library — read and search your highlights and notes in a calm, book-like view (one file per book, in reading order, with your notes).
- Review — each day brings back one line from your highlights, with a shuffle and a per-book filter.
- History — see every saved copy, re-create your files from an older one, and open the folder where everything lives.
- Settings — choose where your library lives (a cloud folder, if you like) and pack everything into a single ZIP archive.
It uses the same safe core as the command line, so it never writes to your
Kobo. The app is purely local: it serves only to 127.0.0.1, makes no network
connections, and requires a per-launch token on every request (see
SECURITY.md). Files default to ~/Markwell — move them from
Settings, or pass --data-dir — and the app always shows you where they
are. It needs only the Python standard library — no extra dependencies, no
build step.
Review & share cards
Each day the Review view brings back one line from your own highlights — the same line until tomorrow — with a shuffle and a per-book filter when you want more. And any highlight can become a share card: an image in three sizes and three styles, with CJK-aware typography and an optional watermark. Cards are drawn on a local canvas; nothing leaves your machine.
Your data, your languages
The whole interface speaks English, 繁體中文, 日本語, and 한국어 — switch
languages from the sidebar; the choice is remembered. Exports are localized
too: the scaffolding of the Markdown and HTML files — titles, counts, table
headers — is written in your language. The app passes your interface language
along automatically; the command line takes --lang en|zh-TW|ja|ko. Your
highlights and notes themselves are always verbatim, never translated.
CSV and Anki column headers (and JSON keys) deliberately stay English: they are machine-facing identifiers, and tools like Notion or Anki map fields by those exact names — translating them would break every import recipe.
Back up to your cloud
Everything Markwell saves lives in one ordinary folder. Open Settings, pick iCloud Drive, Google Drive, Dropbox, or OneDrive, and Markwell copies your library there — nothing is ever deleted, and Markwell itself never uploads a byte: your cloud app syncs the folder like any other. The same screen can pack everything into a single ZIP archive. Step-by-step instructions — including moving to a new computer — are in the cloud backup guide.
Command line
Plug in your Kobo, then:
markwell # snapshot the device, then export every format
markwell --format md # one format: md, json, csv, anki, or html
markwell --format md,csv # any comma list of those ("all" = every format)
markwell --lang ja # language for export labels: en, zh-TW, ja, ko
markwell --snapshot-only # just back up the database, no export
markwell --db PATH # export from an existing snapshot (no device read)
markwell --device PATH # Kobo mount point OR KoboReader.sqlite path (overrides auto-detect)
markwell --require-device # fail instead of falling back to the latest local snapshot
markwell --out DIR # output directory (default: output/, relative to the current directory)
markwell --debug # show full tracebacks on error
markwell --version # print the version and exit
Progress and status messages go to stderr; the exported data and JSON are
written to files under --out. On success the tool prints the absolute path of
the output directory, so you always know exactly where the files landed.
Output (backups/ and output/ are created relative to the current directory):
backups/
└── KoboReader-YYYYMMDD-HHMMSS.sqlite timestamped, never overwritten
output/
├── index.md all books, counts, links
├── <book>.md one file per book, highlights in reading order
├── highlights.json machine-readable export (schema "markwell/1")
├── highlights.csv one row per highlight, for Excel / Numbers / Notion
├── anki.tsv flashcards ready to import into Anki
└── library.html the whole library as a single self-contained page
How it works
detect device → snapshot once (read-only) → read snapshot → render the chosen formats
The device is read at most once per run and never modified. The exports are a
projection of the latest snapshot only; it is the snapshot history that
preserves everything, so a highlight removed on the device is still recoverable
from the dated .sqlite it was last captured in (see Why).
JSON format
{
"schema": "markwell/1",
"generator": "markwell/0.2.0",
"generated": "2026-06-01",
"source": "KoboReader-20260601-101010.sqlite",
"source_freshness": "device",
"books": [
{
"title": "…", "author": "…", "volume_id": "…",
"highlights": [
{ "text": "…", "note": null, "date": "2025-03-17", "chapter_index": 4 }
]
}
]
}
Fields:
schema— the schema contract,markwell/<MAJOR>(see below).generator— the producing tool and its version,markwell/<version>.generated— local date (YYYY-MM-DD) the export was produced.source— filename of the snapshot the export was projected from.source_freshness— whethersourcecame from a live"device"snapshot taken this run, or a"cached_snapshot"reused because no device was connected.books[]— each withtitle,author,volume_id, andhighlights[](text,note,date,chapter_index).
Schema stability
The /N suffix in schema is the major version. It only bumps on a
breaking change (a field removed, renamed, or given a new meaning). Within a
major version, changes are additive only — new fields may appear, so
consumers must ignore unknown fields rather than failing on them. Pin to the
major (markwell/1) and you can rely on every documented field staying put.
Exit codes
| Code | Meaning |
|---|---|
0 |
success |
2 |
no device and no usable snapshot/source |
3 |
database read fine, but contained no highlights or notes |
4 |
source unreadable or its schema is unsupported |
Notes & compatibility
- Tested against Kobo firmware schemas with
Bookmarkandcontenttables. If a firmware update changes the schema, please open an issue. - Note (annotation) support reads
Bookmark.Annotation; if you write notes on highlights, they appear under each highlight. - Exported text is verbatim and untrusted. Highlights and notes are reproduced
exactly as written, so treat the Markdown/JSON as data, not trusted markup — a
value beginning with
=,+,-, or@can be read as a formula if you import it into a spreadsheet/CSV, so sanitize on import if that matters. See SECURITY.md.
Development
pip install -e ".[dev]"
pytest
See CONTRIBUTING.md for the architecture invariants and house rules, CHANGELOG.md for what's changed, and SECURITY.md for how to report a vulnerability.
Maintainer
Built and maintained by Eric Tu (@ceparadise168) — ceparadise168@gmail.com. Markwell is free, with no donations accepted for now — if it helped you preserve your reading, star the repo and share a quote card.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file markwell-0.2.0.tar.gz.
File metadata
- Download URL: markwell-0.2.0.tar.gz
- Upload date:
- Size: 141.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ae3c75387b17701d5912e5c5a5e474ecb6a107990db30033c676c7843ea4407
|
|
| MD5 |
5be43a4cd2d327d41fb84cb6b1cd8698
|
|
| BLAKE2b-256 |
b56b11b84a34a9f6fc69531d7769ecbde95f2f9e18b03e9e5c2d7ec4bc5f4d4e
|
Provenance
The following attestation bundles were made for markwell-0.2.0.tar.gz:
Publisher:
publish-pypi.yml on ceparadise168/markwell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
markwell-0.2.0.tar.gz -
Subject digest:
6ae3c75387b17701d5912e5c5a5e474ecb6a107990db30033c676c7843ea4407 - Sigstore transparency entry: 1803631107
- Sigstore integration time:
-
Permalink:
ceparadise168/markwell@9d13b076a1dfc72cce130b849263af4e7aa0632d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ceparadise168
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@9d13b076a1dfc72cce130b849263af4e7aa0632d -
Trigger Event:
release
-
Statement type:
File details
Details for the file markwell-0.2.0-py3-none-any.whl.
File metadata
- Download URL: markwell-0.2.0-py3-none-any.whl
- Upload date:
- Size: 116.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d27cc2df1a16509248c5a7e8e2d5191107c819ebd2e2c270955d30e13cca196
|
|
| MD5 |
99c17cab9d793ab370332e40dc8ac0f7
|
|
| BLAKE2b-256 |
8024f531746e76c6637550713f4d497b5a447d2b777ebfd11a3d9db63d6e1e1c
|
Provenance
The following attestation bundles were made for markwell-0.2.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on ceparadise168/markwell
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
markwell-0.2.0-py3-none-any.whl -
Subject digest:
5d27cc2df1a16509248c5a7e8e2d5191107c819ebd2e2c270955d30e13cca196 - Sigstore transparency entry: 1803631275
- Sigstore integration time:
-
Permalink:
ceparadise168/markwell@9d13b076a1dfc72cce130b849263af4e7aa0632d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ceparadise168
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@9d13b076a1dfc72cce130b849263af4e7aa0632d -
Trigger Event:
release
-
Statement type: