Syncs reading progress and sessions between KOReader and your Tome library.
- Open Tome in your browser at
http://<your-server>:<port>/settings - Scroll to the KOReader section
- Click Download Plugin
- Extract the ZIP to
koreader/plugins/on your device so you havekoreader/plugins/tomesync.koplugin/main.lua - Restart KOReader
The plugin is pre-configured with your server URL and API key. No manual configuration needed.
Important: Download the plugin from the same address your e-reader can reach. If your server is at <your-server-ip>:8080, open the settings page at that address before downloading.
- Reading sync -- position, progress, and reading sessions sync between KOReader and Tome's web reader
- Rating sync -- KOReader's native star rating and review sync both ways with Tome
- Reading-history import -- backfill Tome's Stats with KOReader's own per-page reading history from before TomeSync (reading time and pages only -- never your read/unread status)
- Series browser -- browse your library's series and download entire series to your device in one tap
- Offline-safe -- everything works seamlessly when your server is unreachable; sessions queue and flush later
- Context-aware menu -- different options when a book is open vs. from the home screen
When you open a book, the plugin tries to match the file to a book in your Tome library:
- Checks its local cache for a known mapping
- If not cached, sends the filename to
GET /api/tome-sync/resolve?filename=<name> - The server matches by file path, title, and volume number (e.g.
Vol. 1in the filename matchesseries_index = 1) - Once resolved, the mapping is cached locally so future opens are instant
Books downloaded via OPDS are matched automatically. Books sideloaded from other sources will also match as long as the filename contains the book title.
If no match is found, the plugin silently skips sync for that book.
| Event | What happens |
|---|---|
| Open a book | Pulls latest position from server. If the server is ahead, jumps to that position. Also reconciles the book's rating with Tome. |
| Every 50 page turns | Pushes current position to server (heartbeat). |
| Close the lid (suspend) | Pushes position, records a reading session, and pushes a rating you set this session. |
| Open the lid (resume) | Starts a new session, pushes position, and flushes any pending offline sessions. |
| Close a book | Pushes final position, records a reading session, and pushes a rating you set this session. |
| "Sync now" in menu | Manual push of current position and flushes pending offline sessions. |
Progress saved in Tome's built-in web reader is written to TomeSyncPosition on the server. When you next open the same book in KOReader, the plugin detects the server is ahead and jumps to the position you left off at in the browser. This makes the web reader and KOReader fully bidirectional — you can switch between them without losing your place.
Every time you close the lid or close a book, the plugin records a session with:
- Start and end timestamps
- Duration in seconds
- Progress at start and end (percentage)
- Number of page turns
- Device name
Sessions shorter than 10 seconds are filtered out. Each session has a unique ID to prevent duplicates.
These sessions power the Stats page in Tome, showing reading time, streaks, and charts.
KOReader keeps its own per-page reading log in statistics.sqlite3 — often going back
years, long before you installed TomeSync. The plugin can import that log into Tome so
your Stats page reflects reading from before TomeSync existed.
- Enable TomeSync > Auto-sync reading history on launch, or run it once from TomeSync > Sync reading history (also assignable to a gesture).
- The first sync pushes your entire history; it is chunked and resumable — the server tracks a per-device watermark and ignores duplicates, so it survives the device sleeping or Wi-Fi dropping mid-sync. Later syncs send only new reading.
- It imports reading time and pages only — it never changes your read/unread status. That stays yours to set (only the live progress sync above moves status).
- Books are matched to your library by title/series/volume; anything the matcher can't place confidently is left out rather than guessed at, so nothing wrong appears in your stats. Multiple devices each sync their own history without double-counting the same reading.
The server automatically updates the book's reading status based on progress:
- unread to reading when progress > 0%
- reading to read when progress >= 99%
The plugin can browse and download entire series directly to your device.
From the home screen (no book open):
- Open the wrench menu, find TomeSync > Browse series
- A list shows all series in your Tome library with book counts and authors
- Tap a series to download all books in it
From inside a book:
- Download full series -- downloads every book in the current book's series
- Download rest of series -- downloads only books after your current volume (based on series_index)
How downloads work:
- Books are organized by book type and series:
<home_dir>/<book_type>/<Series Name>/(e.g.books/manga/One Piece/,books/light_novel/Black Summoner/). The base directory is KOReader's home folder if set, falling back todownload_dirthenlastdir. - The book type (manga, light_novel, book, comic) comes from Tome's metadata
- Filenames mirror Tome's OPDS naming:
Vol. N — Title.extfor volumes in a series, plainTitle.extotherwise. This keeps light-novel volumes that share a title from clobbering each other. - Format preference: epub > kepub.epub > cbz > pdf > mobi > azw3
- Books already on the device are skipped (matched by book ID, not filename)
- A live progress popup shows "Downloading N of M — Title" as each book is fetched
- Downloaded books are automatically registered in the book map, so sync works immediately when you open them
- A summary shows downloaded/skipped/failed counts when finished
By default downloads are filed as described above (<book_type>/<Series Name>/Vol. N — Title.ext, standalones under their author). Settings → Download location & naming changes this. The setting applies to every way books reach the device — the series browser, the in-book series downloads, and the Send-to-KOReader inbox all share one downloader. It is per-device and stored in KOReader's own settings, so your phone can use a different layout than your e-reader, and it survives plugin updates.
Three options:
- Default (type and series folders) — the layout above. This is also what runs when no template is set, on exactly the same code path as before the setting existed.
- Flat in home folder — every book lands directly in the home folder, named
Series - NN - Title.ext. Series and volume are dropped for books that have none, so a standalone is justTitle.ext. For people who keep everything unsorted in one place. - Custom template — build the path yourself, Sonarr/Radarr-style.
A template is a free-form string: tokens, literal text, and / folder separators in any combination. The dialog pre-fills a suggestion ({book_type}/{series}/{volume:00} - {title}) purely as a starting point — delete it and write anything. Tokens can repeat, literals can appear anywhere, folders can nest as deep as you like.
Token reference (the complete set — anything else is rejected):
| Token | Renders as | Empty when |
|---|---|---|
{book_type} |
Tome book-type slug (manga, light_novel, novel, ...) |
never (falls back to book) |
{series} |
series name | book has no series |
{volume} |
series index as-is (3, 12, 1.5) |
book has no series index |
{volume:00} |
zero-padded series index (03); pad width = number of zeros, so {volume:000} gives 003. Fractional volumes (1.5) are never padded. |
book has no series index |
{title} |
book title | never |
{author} |
author name | author unknown |
Case modifiers: wrap a token name in Lower(...) or Upper(...) — {Lower(series)}, {Upper(book_type)}. No inner braces: {Lower({series})} is not valid syntax.
Rules:
/starts a new folder. Everything between slashes becomes one folder (or, for the last part, the filename).- The file extension (
.epub,.cbz, ...) is appended automatically — never write it in the template. - Anything that isn't a token is literal text and is kept verbatim.
Examples (Berserk vol. 3, "The Egg of the King", Kentaro Miura, type manga):
| Template | Result |
|---|---|
{title} |
The Egg of the King.epub |
{series} - {volume:00} - {title} |
Berserk - 03 - The Egg of the King.epub (this is the flat preset) |
Tome/{series}/{title} |
Tome/Berserk/The Egg of the King.epub — literal folder name |
{author}/{series} {volume:000} {title} |
Kentaro Miura/Berserk 003 The Egg of the King.epub |
{Upper(series)}/vol{volume} - {Lower(title)} |
BERSERK/vol3 - the egg of the king.epub |
{book_type}/{series}/{series} v{volume} |
manga/Berserk/Berserk v3.epub — tokens may repeat |
Books without a series (or volume, or author): empty tokens disappear, together with orphaned separators around them — spaces, -, _ and , left dangling by a vanished token are tidied up, and a folder segment that renders entirely empty is dropped. So one template serves series books and standalones:
| Template | Series book | Standalone |
|---|---|---|
{series} - {volume:00} - {title} |
Berserk - 03 - The Egg of the King.epub |
Frankenstein.epub |
{book_type}/{series}/{volume:00} - {title} |
manga/Berserk/03 - The Egg of the King.epub |
novel/Frankenstein.epub (series folder dropped) |
One caveat: the cleanup removes orphaned separators, not orphaned words. A literal glued to a token — Vol. {volume} — leaves Vol behind when the book has no volume (Vol - Title.epub). If your library mixes series books and standalones, keep bare separators around tokens that can be empty ({volume:00} - {title}), the way both presets do. There is no conditional syntax ("include this text only when the token exists") — if you need that, open an issue.
Validation and safety:
- Templates are validated when you tap Save: an unknown token (or a template that renders to nothing) is rejected on the spot with an error, and a valid template shows a preview of the resulting filename. A typo can't silently misfile your downloads.
- Every path segment is sanitized for the filesystem, and
..segments are discarded — a template can never place files outside the download base folder. - A
/inside a metadata value (a title likeFate/Zero) becomes-; only slashes you write in the template create folders. - If a saved template somehow renders empty for a particular book (e.g. it only uses
{series}and the book has none), that book falls back to the default layout rather than failing. - Leave the dialog's input empty and save to clear the template and restore the default layout.
Changing the setting later only affects future downloads: books already on the device are remembered by book ID and stay skipped regardless of where they live, and previously downloaded files are not moved.
The plugin is designed to work seamlessly when your server is not reachable -- whether you are on a plane, commuting without VPN access, or your server is simply down.
- No WiFi at all: Every sync request is skipped instantly. There is zero delay or freezing. You will not notice anything different while reading.
- WiFi connected but server unreachable (e.g. public WiFi without VPN): Requests time out after 5 seconds. After 3 consecutive failures, the plugin stops trying for the rest of the session. No further delays.
- Reading sessions are saved locally: If a session cannot be sent to the server (because you are offline when you close the lid), it is saved to disk and retried automatically the next time you open the lid with a working connection.
- You start reading on the train (no server access)
- All sync silently skips -- reading is completely uninterrupted
- You close the lid -- the session is saved locally on your device
- You get home, open a book -- within the first 50 page turns, the plugin flushes all saved sessions to the server
- Your Stats page and reading progress are up to date
Sessions also flush when you tap "Sync now" in the menu, or when WiFi reconnects and you resume reading.
- Up to 50 sessions can be saved locally. If you go offline for an extremely long time, the oldest sessions are dropped to prevent unbounded storage use.
- Sessions saved locally survive KOReader restarts -- they are stored in KOReader's settings file.
- The backoff counter (3 failures before giving up) resets automatically on the next successful request. You can also reset it manually via Settings → Test connection in the plugin menu.
The plugin menu is context-aware. It self-registers in the wrench menu (after Calibre) under TomeSync and shows different options depending on whether a book is open.
| Option | Description |
|---|---|
| Browse series | Opens the series browser. Lists all series with book count and author. Tap to download. |
| Sync reading history | Imports KOReader's per-page reading log into Tome's Stats (time and pages only). First run backfills everything; chunked and resumable. See Reading-History Import. |
| Settings | Submenu with persistent options and diagnostics (see below). |
| About | Version info (semver + build). |
| Option | Description |
|---|---|
| Download full series | Downloads all books in the current book's series. |
| Download rest of series | Downloads books after the current volume only. |
| Sync now | Pushes current position, highlights/notes, and flushes any pending offline sessions. |
| Tracking: on / paused | Pauses automatic session tracking and syncing for the current KOReader run. Resets to on at the next start — it is not a permanent setting. Manual actions (Sync now, downloads) still work while paused. |
| Pending sessions (N) | Shows how many sessions are queued for sync. Tap for details. |
| Option | Description |
|---|---|
| Auto-connect WiFi when needed | Opt-in toggle, off by default. When a TomeSync action needs the server and WiFi is down, KOReader re-establishes the connection first (honouring your KOReader WiFi prompt/auto-enable setting), then runs the action. Helps devices that aggressively sleep WiFi, e.g. PocketBook. Only user-initiated actions reconnect — background tracking never turns the radio on. |
| Download location & naming | Where downloads (series, inbox) are filed: the default type/series layout, flat in the home folder, or a custom token template. Per-device. See Download location & naming. |
| Test connection | Verifies the server is reachable. Also resets the backoff counter if the server was previously unreachable. |
| Re-resolve all books | Wipes the local filename-to-book-ID cache. Use if a book matched incorrectly. |
| Check for updates | Fetches the latest plugin build from your server and installs it if newer (then prompts to restart). |
| Auto-check for updates on launch | Opt-in toggle. When on, TomeSync checks for updates shortly after startup and prompts only when one is available. |
| Auto-sync reading history on launch | Opt-in toggle, off by default. When on, TomeSync pushes new KOReader reading history to Tome shortly after startup (the first run backfills your whole history; chunked and resumable). Reading time and pages only — never your read/unread status. See Reading-History Import. |
TomeSync registers bindable gesture actions (KOReader Settings → Taps and gestures → Gesture manager, listed under General — available in both the reader and the file manager):
| Action | Does |
|---|---|
| TomeSync: Open menu | Pops the full context-aware TomeSync menu as a standalone popup. |
| TomeSync: Browse series | Jumps straight to the series browser/downloader. |
| TomeSync: Sync highlights | Pushes the current book's highlights and notes to Tome immediately. |
| TomeSync: Sync reading history | Imports KOReader's per-page reading history into Tome's Stats (time and pages only). |
Highlights and notes you make in KOReader sync to Tome — and to your other KOReader devices. They also appear in a Highlights & Notes section on the book's detail page (the highlighted text, your note, and the chapter). Sync happens automatically alongside position sync — when you open a book (pulls others' highlights), on suspend, on closing the book, and via Sync now — and can be triggered manually with the TomeSync: Sync highlights gesture.
How conflicts resolve. Identity is the highlight's position, so the same passage highlighted on two devices is one highlight. Edits to the same highlight's note resolve last-write-wins (newest edit). Deletes propagate via tombstones — a highlight you remove on one device is removed on the others and won't reappear. Device→device sync needs both devices to have the Tome-served copy of the book (identical file → matching positions); a sideloaded different copy won't line up.
Rendering highlights inside the web reader is a separate, later step — for now the web side shows them as a list on the detail page.
KOReader has its own Book status screen with a 1–5 star rating and a review (long-press a book → Book status, or the end-of-book screen). The plugin keeps that in step with the rating and review on Tome's book detail page, both ways:
- Rate a book on the web and the next time you open it in KOReader (for a book that came from Tome), the stars and review are written into the book.
- Rate a book in KOReader and it flows up to Tome when you close or suspend.
Ratings are per user and private to you. Reading status (reading / finished) is left untouched — that already syncs separately via reading progress.
How conflicts resolve. The plugin remembers the last value it synced for each book, so normally only the side that actually changed is sent. If a book's rating changed on both the web and the device since the last sync, Tome wins — it is the single source of truth. As with highlights, this needs the Tome-served copy of the book so the plugin can match it to the right library entry; a sideloaded different copy won't sync its rating.
The plugin authenticates with an API key (Authorization: Bearer tk_...), not your login password. Keys are managed in Tome's settings page:
- A key is auto-created when you first download the plugin
- You can create additional keys or revoke existing ones
- Revoking a key immediately disables any plugin using it
Once the plugin is installed, it updates itself — open the TomeSync menu and tap Settings → Check for updates (or enable Auto-check for updates on launch). It downloads the latest build from your server, swaps it in atomically, and asks you to restart. Your server URL, API key, and reading history carry over automatically.
The plugin version is shown on the Settings page next to "TomeSync Plugin" (v<semver> (build N)); the build integer is what drives update comparisons.
The plugin is split into a frozen shim (main.lua, never replaced) and a replaceable implementation (main_impl.lua). On every load the shim runs a rollback state machine backed by main_impl.lua.bak:
- A download that fails validation (too small, doesn't compile, missing sentinels) is rejected before anything is swapped — files untouched.
- A syntax-broken update fails to load and is rolled back on the same launch.
- An init-crashing update doesn't confirm itself, so the shim rolls it back on the next launch.
- If even the backup is unusable, the shim loads a valid inert stub ("TomeSync failed to load — reinstall"); KOReader itself is never affected.
The first build carrying the shim+impl must be installed by hand once (every update after that is in-app). This is also the fallback if you ever need to force a clean reinstall.
# 1. Download plugin ZIP from Tome Settings page, unzip it
# 2. Deploy in one command:
ssh root@<kindle-ip> "rm -rf /mnt/us/koreader/plugins/tomesync.koplugin" && \
scp -r tomesync.koplugin root@<kindle-ip>:/mnt/us/koreader/plugins/
# 3. Restart KOReader from its menu: Settings > Device > Restart KOReaderNo cable or USB mode needed. Requires SSH enabled on KOReader (Settings > Network > SSH server).
- Exit KOReader
- Connect via USB cable
- Replace
koreader/plugins/tomesync.koplugin/with the new version - Eject and restart KOReader
The downloaded plugin has your server URL and API key baked in, so there is nothing to configure after extracting.
Plugin does not appear in the menu:
- Make sure the folder structure is
koreader/plugins/tomesync.koplugin/main.lua(not double-nested) - Restart KOReader after installing
- Check
koreader/crash.logfor errors
"Connection failed" on test:
- Verify your server is running and reachable from the e-reader's network
- If you are on a VPN (e.g. Tailscale), make sure it is connected on the e-reader
- Re-download the plugin if your server address changed
Wrong book matched:
- Use "Re-resolve all books" in the plugin menu, then re-open the book
- The resolve endpoint matches by title and volume number; ambiguous titles may match incorrectly
Progress shows 0% on the web after syncing:
- The plugin only syncs when the book is matched to a Tome book ID
- Check the plugin menu: if "Sync now" is greyed out, the book is not matched
Stats page is empty:
- Sessions are recorded when you close the lid or close a book. If you just started using the plugin, read for a while and close the lid to generate your first session.
- If you were previously using an older version of the plugin that only recorded sessions on book close, re-download the latest version. The new version records a session every time you close the lid.
KOReader froze or became unresponsive:
- Older versions of the plugin could freeze KOReader for up to 60 seconds per request when the server was unreachable. Re-download the latest version, which has a 5-second timeout and automatic backoff.
The plugin consists of three files (plus a backup created on first update):
| File | Purpose |
|---|---|
_meta.lua |
Plugin metadata (name, description) |
main.lua |
Frozen stable shim — loads the implementation and runs the anti-brick rollback state machine. No config; never replaced by self-update. |
main_impl.lua |
All plugin logic, HTTP client, and config (server URL, API key baked in at download time). The only file self-update replaces. |
main_impl.lua.bak |
Last confirmed-good implementation, written automatically before each update so the shim can roll back. |