writingartikelen

Kobo highlights into Readwise — no desktop app, just the browserKobo-aantekeningen naar Readwise — zonder desktop-app, gewoon in de browser

I borrow my books from the online library and read them on my Kobo. While reading I highlight a lot — lines that stick, things I want to keep. I want those annotations in Readwise, where the rest of my highlights already live. Except that doesn’t happen by itself.

Books from the Kobo store, Kobo Plus, and library loans already sync through Readwise’s own integration. But sideloaded books — EPUBs you put on the device yourself via Calibre, email, or USB, or a loan from the online library that you transfer to your Kobo with Adobe Digital Editions — are a different story. Their highlights live only in the local KoboReader.sqlite on the e-reader and never reach the Kobo cloud, so Readwise can’t get at them.

The existing fix is a desktop app

For exactly this problem there’s October: a tidy, open-source desktop app that detects your Kobo the moment you plug it in, reads the database, and uploads the highlights to Readwise. It works well. But it’s still something you install, per operating system, and keep around.

I figured this can just run in the browser now. Chrome and Edge can read a file on your disk without uploading it via the File System Access API, and sql.js runs SQLite as WebAssembly right inside the tab. Together that’s everything you need: no install, one URL.

How it works

The flow is short:

pick the fileread SQLite in the browserforward highlights to Readwise.

You choose your KoboReader.sqlite, sql.js reads it locally (the file never leaves your computer), and the app shows, per book, which highlights it will send. A thin proxy takes that batch and forwards it to the Readwise API — that’s all the server does.

The trust model: the token is forgotten

There is one thing the server does touch: your Readwise token, because it has to go into the Authorization header to Readwise. So the whole design is built around it — the proxy forwards your token and then immediately forgets it. No request-body logging, no persistence, no database. The token lives only in memory for the duration of a single request. That’s the entire point of the app, and it’s stated as an explicit promise at the top of app.py.

For the same reason sql.js is self-hosted rather than loaded from a CDN: on the page that briefly holds your token, you don’t want any third-party script running.

Finding the file: showing hidden folders

The KoboReader.sqlite sits in a hidden folder called .kobo, at the root of your e-reader when you mount it over USB. Hidden folders — anything starting with a dot — don’t show by default in Finder or in the macOS file dialog.

The trick: in Finder or the “Choose file” dialog, press Shift + Command + . (period). That toggles hidden files on and off; now the .kobo folder appears and you can navigate into it to KoboReader.sqlite. The same shortcut again hides them.

Why Chromium-only, desktop-only

The File System Access API today only works in Chrome or Edge on desktop. Firefox, Safari, and mobile browsers don’t support it. Rather than failing cryptically, the app shows a clear notice in those cases: this is a Chromium desktop tool. That’s the cost of installing nothing — and for me a fine trade.

Hosting

Runs on Fly.io as a single scale-to-zero app at kobo2readwise.fly.dev. Because every user enters their own token in the browser, the server needs no Readwise secret at all. Deploys go through release-please: a real version bump (merging the release PR) triggers a production deploy; ordinary feature merges don’t.

The stack, in one line: Python 3.14+, FastAPI for the proxy, sql.js (SQLite as WASM) and the File System Access API for reading in the browser, vanilla JS for the UI, and uv for packaging.

Ik leen mijn boeken uit de online bibliotheek en lees ze op mijn Kobo. Tijdens het lezen markeer ik van alles — zinnen die blijven hangen, dingen die ik wil onthouden. Die aantekeningen wil ik in Readwise hebben, waar de rest van mijn highlights ook staat. Alleen: dat gaat niet vanzelf.

Boeken uit de Kobo-winkel, Kobo Plus en bibliotheekleningen synct Readwise zelf al. Maar zelf-geüploade boeken — EPUB’s die je via Calibre, mail of USB op het apparaat zet, of bijvoorbeeld een lening uit de online bibliotheek die je met Adobe Digital Editions op je Kobo zet — zijn een ander verhaal. Hun highlights leven alleen in de lokale KoboReader.sqlite op de e-reader en bereiken de Kobo-cloud nooit, dus Readwise kan er niet bij.

De bestaande oplossing is een desktop-app

Voor precies dit probleem bestaat October: een keurige, open-source desktop-app die je Kobo herkent zodra je ’m inplugt, de database uitleest en de highlights naar Readwise uploadt. Werkt prima. Maar het is wél iets dat je installeert, per besturingssysteem, en bijhoudt.

Ik dacht: dat kan tegenwoordig ook gewoon in de browser. Chrome en Edge kunnen met de File System Access API een bestand op je schijf lezen zónder het te uploaden, en sql.js draait SQLite als WebAssembly ín het tabblad. Samen is dat alles wat je nodig hebt: geen installatie, één URL.

Hoe het werkt

De flow is kort:

bestand kiezenSQLite lezen in de browserhighlights doorsturen naar Readwise.

Je kiest je KoboReader.sqlite, sql.js leest ’m lokaal uit (het bestand verlaat je computer nooit), en de app laat per boek zien welke highlights ze gaat sturen. Een dunne proxy neemt die batch aan en zet ’m door naar de Readwise-API — meer doet de server niet.

Het vertrouwensmodel: de token wordt vergeten

Er is één ding dat de server wél aanraakt: je Readwise-token, want dat moet in de Authorization-header naar Readwise. Dus daar is de hele opzet omheen gebouwd: de proxy stuurt je token door en vergeet ’m daarna meteen. Geen logging van request-bodies, geen opslag, geen database. De token leeft alleen in het geheugen voor de duur van één verzoek. Dat is het hele punt van de app — en het staat als expliciete belofte boven in app.py.

Om diezelfde reden is sql.js self-hosted in plaats van van een CDN geladen: op de pagina die tijdelijk je token vasthoudt, wil je geen script van derden laten draaien.

Het bestand vinden: verborgen mappen tonen

De KoboReader.sqlite zit in een verborgen map genaamd .kobo, in de root van je e-reader wanneer je ’m via USB aankoppelt. Verborgen mappen — alles wat met een punt begint — zie je standaard niet in Finder of het bestandsdialoog van macOS.

De truc: druk in Finder of in het “Kies bestand”-venster op Shift + Command + . (punt). Dat schakelt verborgen bestanden aan en uit; nu verschijnt de .kobo-map en kun je erin naar KoboReader.sqlite navigeren. Nog een keer dezelfde toetscombinatie verbergt ze weer.

Waarom alleen Chromium, alleen desktop

De File System Access API werkt vandaag alleen in Chrome of Edge op desktop. Firefox, Safari en mobiele browsers ondersteunen ’m niet. In plaats van cryptisch te falen toont de app in die gevallen een duidelijke melding: dit is een Chromium-desktop-tool. Dat is de prijs van geen-app-installeren — en voor mij een prima ruil.

Hosting

Draait op Fly.io als één scale-to-zero-app op kobo2readwise.fly.dev. Omdat elke gebruiker z’n eigen token in de browser invoert, heeft de server geen enkel Readwise-geheim nodig. Deploys gaan via release-please: een echte versiebump (het mergen van de release-PR) triggert een productie-deploy, gewone feature-merges niet.

De stack, in één regel: Python 3.14+, FastAPI voor de proxy, sql.js (SQLite als WASM) en de File System Access API voor het lezen in de browser, vanilla JS voor de UI, en uv voor packaging.

pythonfastapisqlitewasmreadwise

← all writing← alle artikelen