A personal reading log you host yourself, for free. Track what you're reading, what you've read (and re-read), who recommended what, and what you thought, with book details filled in from OpenLibrary. It installs to your phone's home screen, works offline, and can follow your audiobook progress on Spotify.
BookBox is for one reader per copy: you deploy your own, on your own free accounts, and your library stays yours. It's built with Next.js and SQLite (a local file, or Turso when hosted).
- Add books in seconds: search OpenLibrary by title, author or ISBN, or scan the barcode on the back with your phone's camera. The title, author, description, publisher, year, pages and cover fill in automatically.
- Search as you type: titles, authors (including names in their original script), series, tags, ISBNs, and your reviews, quotes and notes. Accents don't matter, words match by their start, and small typos are forgiven ("kill crek" finds Kill Creek).
- Shelves and filters: Currently Reading, To Read, Read and Abandoned, plus year read, tag, person, rating, and owned/Kindle/borrowed.
- Lists: your own collections, like Urbanism or Gifts to give, in the sidebar. Add a book from its page; a book can be on any number. A tag (such as an imported Goodreads shelf) can be turned into a list in one step.
- Reads and progress: every read is logged separately, so re-reads are tracked. Progress is set by page (or by percentage for Kindle books) and shows on your Currently Reading shelf.
- Spotify audiobooks (optional): when you start or resume an audiobook on Spotify, its book moves to Currently Reading (and is added if it isn't in your library yet), with your listening progress.
- Offline: once opened online, the home screen app keeps working without a connection. Search, covers and the main pages stay available, and changes to status, ratings, reads, progress and notes are saved on your device and sync when you're back online.
- People and sharing: note who recommended a book and who it's for, and give anyone a read-only page ("Books for Katie") at an unguessable link, with its own title, message, layout, accent colour, and whether your ratings and reviews show. Private notes and spoilers are never shared.
- Stats: a reading goal per year, books per year, ratings, top tags and authors, how many of the books you read were written by women, men and nonbinary authors (looked up on Wikidata, and yours to correct on the Authors page), and whose recommendations you rate highest, plus a reading calendar and a year in review for every year.
- Series (optional, with Hardcover): each series page lists the books in the series you don't have yet, from Hardcover, with a button to add one to To Read. Series Hardcover doesn't know are left off the Series page.
- Lending: note who has your copy of a book and when it's due back, with a history of past loans.
- Tags (rename, merge, delete), library borrowing with due dates, and tidy-up tools in Settings for missing covers, missing ISBNs, duplicates and a bulk OpenLibrary refresh.
- Import from Goodreads: bring in your Goodreads export (books, shelves, ratings, reviews and finish dates) from Settings. Books you already have are flagged so you choose which to add, and covers are fetched from OpenLibrary.
- Import from Libby: bring in your Libby timeline export (books you've borrowed, returned and placed holds on) from Settings. Returned books count as read on the day you returned them, unless you mark them as not finished.
- Export your whole library as CSV at any time.
BookBox runs within the free tiers of Vercel (the app), Turso (the database) and Cloudflare R2 or Vercel Blob (cover storage). You'll need a GitHub account and about ten minutes.
- Fork this repository on GitHub.
- Create a Vercel project from your fork (Vercel → Add New → Project → import the repo). The first deploy will show a locked app until the next steps are done; that's expected.
- Add a database. In the project, open Storage → Create Database → Turso, pick the free plan, and connect it to the project. That sets
TURSO_DATABASE_URLandTURSO_AUTH_TOKEN. (Or create a database at turso.tech yourself and set those two variables.) - Add cover storage. Either:
- Cloudflare R2 (more room: 10 GB and millions of reads a month free). In the Cloudflare dashboard, create an R2 bucket (leave public access off), then under R2 → Manage API tokens create a token with Object Read & Write on that bucket. Add
R2_ACCOUNT_ID,R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEYandR2_BUCKETto the Vercel project's environment variables. - Vercel Blob (fewer steps). Still under Storage, create a Blob store with Private access and connect it. That sets
BLOB_READ_WRITE_TOKEN. Its free tier allows 10,000 reads a month; past that, Vercel blocks the store for 30 days, so this suits a single small library.
- Cloudflare R2 (more room: 10 GB and millions of reads a month free). In the Cloudflare dashboard, create an R2 bucket (leave public access off), then under R2 → Manage API tokens create a token with Object Read & Write on that bucket. Add
- Set a setup password and a secret. Under Settings → Environment Variables, add
BOOKBOX_PASSWORD(a long passphrase) andBOOKBOX_SECRET(a long random string, for example fromopenssl rand -base64 32). Mark both Sensitive. - Redeploy (Deployments → ⋯ → Redeploy), open your site, and sign in with your email and that password. The first sign-in creates your account; after that the email and password are your sign-in, and the password can be changed in Settings. The database is set up on first use. Then add your name in Settings too.
Your library is at /library. Visitors who aren't signed in get a page about BookBox, with links to sign in and sign up.
The owner (the first account) can invite people from Settings → People: each invite link works once, for two weeks. Everyone who joins gets an empty library of their own, in a Turso database BookBox makes for them, which nobody else can see. That needs a Turso Platform API token: in Turso's dashboard (from Vercel, open your database and choose Open in Turso), make one under Settings → API Tokens, and set TURSO_API_TOKEN (Sensitive) and TURSO_ORG (the name in the dashboard's address) in Vercel. Turso's free plan has room for a few hundred databases.
Covers for everyone share your cover storage and its free tier. Each library can keep up to 5,000 covers. To connect Spotify, each person needs adding to your Spotify app's users (up to 25).
On iPhone, open your site in the browser, tap Share, then Add to Home Screen. The home screen app keeps its own sign-in, so you sign in once inside it.
These are for the server: the password and the services BookBox runs on. Preferences, like your name, are on the Settings page in the app and saved with your library.
| Variable | Required | What it's for |
|---|---|---|
BOOKBOX_PASSWORD |
Yes, when hosted | The password for the first sign-in, which creates your account (with the email you enter). Not used once an account exists. When deployed on Vercel, the app stays locked until it's set. |
TURSO_DATABASE_URL, TURSO_AUTH_TOKEN |
Yes, when hosted | The database. Without them, BookBox uses a SQLite file in DATA_DIR. |
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET |
Yes, when hosted (or Blob) | Cover storage in a Cloudflare R2 bucket. Used over Blob when both are set. Without either, covers are files in DATA_DIR. |
BLOB_READ_WRITE_TOKEN |
Yes, when hosted (or R2) | Cover storage in a private Vercel Blob store. |
STORAGE_CYCLE_DAY |
No | The day of the month your storage service's billing period starts (Cloudflare's Billing page shows it; default 1). BookBox counts cover uploads and reads per period and pauses them at 80% of the free tier; Settings shows the counts. |
BOOKBOX_SECRET |
Recommended | Signs the sign-in cookie. Defaults to BOOKBOX_PASSWORD; changing it signs everyone out. |
ACCOUNTS_DATABASE_URL, ACCOUNTS_AUTH_TOKEN |
No | A separate Turso database for accounts. Without them, accounts are kept in the library's database. |
TURSO_API_TOKEN, TURSO_ORG, TURSO_GROUP |
To invite people | A Turso Platform API token and your organization's name (and its group, default if unset), so BookBox can make a database for each person who joins. |
SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET |
No | Spotify app keys, if you'd rather set them here than in Settings; see below. |
HARDCOVER_TOKEN |
No | A Hardcover API token, if you'd rather set it here than in Settings; see below. |
DATA_DIR |
No | Where the local database and covers live (default ./data). |
Changing your password in Settings signs out your other devices.
Spotify only lets each developer app serve a handful of users, so everyone uses their own. It's free, but the Spotify account that owns it needs Premium, and Spotify only offers audiobooks in some countries.
- In BookBox, open Settings → Spotify. It shows the redirect URI for your site.
- On the Spotify developer dashboard, create an app, tick Web API, and add that redirect URI.
- Paste the app's Client ID and Client secret into Settings, then tap Connect Spotify.
BookBox only reads your saved audiobooks and listening positions; it never plays or changes anything. It checks when you open the app, at most every few minutes. The first check only records where each audiobook stands, so audiobooks you'd already started aren't all pulled in at once. Add any of them from the Spotify page (linked from Settings), which lists your audiobooks and what they're linked to.
(You can set the keys as SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET on the host instead; those win over Settings.)
Libby has no API for other apps, so BookBox uses the private one the Libby app itself uses (as odmpy and the Libby plugin for Calibre do). It can stop working whenever Libby changes it.
- In the Libby app, open Settings → Copy To Another Device and pick the option that shows an 8-digit code (Libby's help has the steps).
- In BookBox, open Settings → Libby, type the code within a minute, and tap Connect Libby.
BookBox checks your shelf when you open the app, at most every ten minutes. A new loan moves its book to Currently Reading (adding it if it isn't in your library yet) with its library and due date. When a loan goes back, by you or because it was due, the book moves to Read that day if it's still Currently Reading: Libby doesn't say whether you finished, so change any you didn't to Abandoned. New holds you don't have yet go on To Read. The first check takes your shelf as it is, but leaves books you've already marked Read or Abandoned alone. Magazines are skipped.
BookBox only reads your loans and holds. The connection could borrow and return on your cards, though, so it's kept on the server and never copied by pull:cloud. If Libby signs BookBox out, Settings says so; connect again with a new code. For your history from before you connected, use the timeline import below it.
Series pages can show the books in each series that you don't have yet, using Hardcover's series data.
- Sign in to Hardcover (a free account is fine) and open Hardcover API in your account settings.
- Make a token and paste it into Settings → Hardcover. BookBox checks it with Hardcover before saving it.
Each series is looked up once and remembered; the Series page checks a few new ones per visit to stay within Hardcover's rate limits. Hardcover tokens expire, so make a new one if series pages say it was refused. (You can set the token as HARDCOVER_TOKEN on the host instead; that wins over Settings.)
npm install
npm run devWith BOOKBOX_PASSWORD unset there's no sign-in, which is fine on your own machine. The database and covers go in ./data (ignored by git). See .env.example for every setting.
The service worker (offline support) only runs in production builds, so npm run dev always shows fresh code. Try offline mode with npm run build && npm start.
npx playwright install chromium # once
npm testThe tests drive the app in a real browser through everyday actions: adding a book by hand, finding one by search and saving it with its cover (also when OpenLibrary's cover server is down), editing and deleting. Each run starts its own copy of the app on port 3210 with an empty library in e2e/.data, and never reaches the real book catalogs: e2e/catalog-stub.mjs answers for them from e2e/fixtures.mjs. Stop npm run dev first, as Next.js runs one dev server per project. Add a test to e2e/ when you add or change a common action.
If you've been using BookBox locally first, copy it up once, before you start using the hosted copy (this replaces what's in the hosted database, and uploads every cover that isn't there yet):
TURSO_DATABASE_URL=libsql://… TURSO_AUTH_TOKEN=… R2_ACCOUNT_ID=… R2_ACCESS_KEY_ID=… R2_SECRET_ACCESS_KEY=… R2_BUCKET=… npm run push:cloud(Use BLOB_READ_WRITE_TOKEN=… in place of the R2 variables for Blob.) To move covers from Blob to R2, run npm run pull:cloud first so every cover is on your computer, then push:cloud with only the R2 variables.
To work on BookBox against your real library, copy the hosted one down (the local database is backed up to data/backups first):
npm run pull:cloudIt uses the same keys file as push:cloud, saved as .env.cloud (git-ignored) with TURSO_DATABASE_URL, TURSO_AUTH_TOKEN and the cover storage keys (the four R2_ variables, or BLOB_READ_WRITE_TOKEN). With that file in place, npm run dev also shows a Pull latest notice whenever the local copy is more than three days old. Your Spotify sign-in and other server-only records stay on the server.
scripts/import-airtable.mts imports the Airtable base BookBox was first built to replace; it's a starting point for writing an importer for your own spreadsheet.
npm run generate:icons redraws the app icon (defined as SVG in scripts/generate-icons.mts) and writes the manifest icons, favicon and iOS launch screens.
Any host with a persistent disk works too (Fly.io, Railway, Render, a VPS), with the database and covers in DATA_DIR:
npm run build
cp -R .next/static .next/standalone/.next/static && cp -R public .next/standalone/public
DATA_DIR=/path/to/data BOOKBOX_PASSWORD=… node .next/standalone/server.jsAlways set BOOKBOX_PASSWORD on a host others can reach.
Ideas for BookBox go in Feature requests on GitHub Discussions. Search first, and give an existing request a 👍 rather than posting it again: the most-wanted ideas get built first. Bugs go in issues.
BookBox is free and open source. If it's useful to you, you can leave a tip on Ko-fi.
- One password protects everything except share pages, which are only reachable by their unguessable links and never include private notes or spoilers. Covers are served through the app, so they're private too.
- After 10 wrong passwords from one network address in 15 minutes, sign-in from that address pauses for the rest of the 15 minutes.
- Found a problem? Please open an issue, or for anything sensitive, contact the maintainer privately rather than posting details publicly.
AGPL-3.0. You're free to use, change and share BookBox. If you run a modified copy for other people to use, you have to offer them its source code under the same license. The "BookBox is open source" link in the app's sidebar is there for that: point SOURCE_URL in src/lib/constants.ts at your fork.