shelf docs
GitHub

Get started

Configuration

Shelf works with no settings at all. Everything here is optional, for when you want email, a reverse proxy, different folders, or to turn something off.

How to set things

Settings can come from environment variables, from appsettings.json beside the app, or from the command line. Names are written here as Section:Name; as an environment variable, use two underscores instead of the colon.

Written here As an environment variable On the command line
Accounts:AllowSignUp Accounts__AllowSignUp=false --Accounts:AllowSignUp false
Email:Host Email__Host=smtp.example.org --Email:Host smtp.example.org

Accounts

Setting Default What it does
Accounts:AllowSignUp true Whether new accounts can be made, until an admin chooses under Who may sign up on Shelf admin, which then decides. The first account can always be made.
Accounts:SignInsPerMinute 10 Sign-in and sign-up attempts allowed a minute from one address.
DocsUrl https://doodersrage.github.io/shelf/ Where Help and the ? beside a page's name send readers, for a shelf that keeps its own copy of these guides.

Cloudflare Turnstile

Turnstile asks a visitor's browser to show it is one a person is using before signing in, signing up, or asking for a password reset goes through, usually without them doing anything. It is off until both keys are set. Make a widget in the Cloudflare dashboard, under Turnstile, for the host names the shelf is reached at, and copy its keys here.

Setting Default What it does
Turnstile:SiteKey none The widget's site key.
Turnstile:SecretKey none The widget's secret key. Keep it as you would a password.
Turnstile:Hostnames the host of Email:PublicAddress The host names the forms are on, separated by commas. Cloudflare says which site a token was made on, and one made anywhere else is refused. Leave out localhost on a shelf others reach.

With Turnstile on, a form whose check fails, or cannot reach Cloudflare, is refused, so a shelf that cannot reach challenges.cloudflare.com cannot be signed in to with a password; passkeys and single sign-on still work. Scripts signing in with a password stop working too: give them an API token. Server health says whether Turnstile is on, or set up only partly, and why the last form was refused, until a check passes: most often a token made on a host that isn't in Turnstile:Hostnames (or, without it, isn't the host of Email:PublicAddress), such as an address inside your network or one with www.. The widget shows its tick either way, since it has no way of knowing what the shelf expects.

Single sign-on

Off unless Oidc:Authority and Oidc:ClientId are set. See Single sign-on.

Setting Default What it does
Oidc:Authority none The provider's issuer address, such as https://auth.example.org/application/o/shelf/ for Authentik.
Oidc:ClientId none The client id the provider gave Shelf.
Oidc:ClientSecret none Its secret. Leave it out for a public client; PKCE is always used.
Oidc:Name SSO What the button says: Sign in with this.
Oidc:CreateAccounts false Make accounts for new people who sign in through the provider even when Accounts:AllowSignUp is false.
Oidc:Scopes openid profile email The scopes asked for.
Oidc:RequireHttpsMetadata true Set to false only for a provider on plain http:// inside your own network.

Upload limits

The largest file Shelf takes, in megabytes. A larger one is refused with a note saying how large it may be.

Setting Default What it does
Uploads:MaxEbookMegabytes 80 The largest EPUB, PDF, Kindle file, or comic.
Uploads:MaxAudioMegabytes 1024 The largest audiobook, all its tracks together; also the most an upload of many files at once may total.

In Docker, as environment variables:

environment:
  - Uploads__MaxEbookMegabytes=500
  - Uploads__MaxAudioMegabytes=4096

A reverse proxy in front of Shelf has a limit of its own, which refuses a large file before Shelf sees it; raise it to match (for nginx, client_max_body_size 4g;). See HTTPS and a reverse proxy.

Where things are kept

Setting Default What it does
ConnectionStrings:Shelf Data Source=shelf.db The SQLite database. In Docker, /data/shelf.db.
EbookStore:Root ebooks beside the app E-book files. In Docker, /data/ebooks.
AudioStore:Root audio beside the app Audiobooks. In Docker, /data/audio.
CoverStore:Root covers beside the app Cover pictures kept on the shelf. In Docker, /data/covers.
DataProtection:KeysPath keys beside the database The keys that seal sign-in cookies and two-step secrets. Keep it private and keep it with backups, or everyone signs in again after a restore.

PostgreSQL

Shelf keeps its database in SQLite, one file beside everything else, which suits a shelf of any size a household has. To keep it in PostgreSQL instead (13 or newer), say for a server that runs one for everything:

Setting Default What it does
Database:Provider Sqlite Postgres to use PostgreSQL.
ConnectionStrings:Postgres none The database, as Host=db;Database=shelf;Username=shelf;Password=…. Shelf makes its tables on the first start.

The keys, backups, and files stay in the folders above, so /data is still wanted in Docker. A few things differ:

  • Search finds a phrase anywhere, ignoring case, but doesn't rank the places it finds or overlook accents, as SQLite's full-text index does.
  • Snapshots hold pg_dump's copy of the database when pg_dump is on the server (or at Database__PgDump), and otherwise the library as JSON, which Restore takes. Back the database up with PostgreSQL's own tools as well.

Moving an existing shelf. With PostgreSQL set up and empty, run Shelf once with the move command; it copies every reader, book, note, and setting from the SQLite file, ids and all, then stops:

docker run --rm -v shelf-data:/data -e Database__Provider=Postgres -e ConnectionStrings__Postgres="Host=db;Database=shelf;Username=shelf;Password=…" ghcr.io/doodersrage/shelf:latest --move-to-postgres true

Then start Shelf with the same two settings. The SQLite file stays as it was, to go back to.

Running behind a proxy

Setting Default What it does
Hosting:BehindProxy false Trust a reverse proxy's X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-For. Only set it when Shelf cannot be reached except through that proxy, or set Hosting:KnownProxies too.
Hosting:KnownProxies none The reverse proxy's addresses or ranges, such as 172.18.0.0/16 or 10.0.0.2, separated by commas. Forwarded headers from anywhere else are ignored, so a request that reaches Shelf's port directly can't claim another address to get around the sign-in limit. Without it, a proxy at any address is trusted.
Network:AllowPrivateAddresses false Let addresses readers type reach this machine and the local network: a podcast feed, and an Audiobookshelf to import from when a reader who isn't an admin brings one in. Off, those must be on the internet, so the shelf can't be used to look inside the network it runs in. An admin's Audiobookshelf, and notices and webhooks, may always reach the local network; nothing reaches a cloud's metadata service.
Passkeys:Origin the address of each request The address readers open Shelf at, such as https://shelf.example.org, when a proxy hides it. Passkeys are tied to it.
Passkeys:RelyingParty the host name of Passkeys:Origin The name passkeys are made for. Rarely needed.

Email

Without a mail server, an admin resets forgotten passwords and notifications come only by the other ways a reader sets up. Set at least Email:Host and Email:From, and Email:PublicAddress for password resets; then Forgot the password? on the sign-in page sends a reader a link, to the email address on their account.

Setting Default What it does
Email:Host none The SMTP server. Email is on once this and Email:From are both set.
Email:Port 587 Its port.
Email:Security auto auto, starttls, ssl (port 465), or none for a server on the same machine.
Email:User, Email:Password none The account to send with, if the server wants one.
Email:From none The address emails come from. Required: without it, Shelf sends no email, even with Email:Host set.
Email:PublicAddress none Shelf's own address, such as https://shelf.example.org, for links in emails. A password reset needs it (or AllowedHosts set to the shelf's own names): without it, no reset email is sent, since the link would point wherever the request claimed to be from.

Nightly backups

Setting Default What it does
Backup:Enabled true Take a copy of the database each night.
Backup:Hour 3 The hour, in UTC, after which it is taken.
Backup:Keep 7 How many to keep.
Backup:Folder backups beside the database Where they go.
Backup:IncludeFiles false Put every e-book and audiobook in each copy too. They never change once saved, so most people back up their folders separately instead.
Backup:CopyTo none A second folder to copy each backup to, such as another disk or a mounted network share. It keeps as many as Backup:Keep.
Backup:S3:Bucket none A bucket in S3-compatible storage to copy each backup to. Needs the two keys below.
Backup:S3:AccessKey, Backup:S3:SecretKey none The keys for it. Give them only the rights to list, put, and delete in that bucket.
Backup:S3:Endpoint Amazon S3 The service's address for anything but Amazon, such as https://s3.us-west-004.backblazeb2.com, https://<account>.r2.cloudflarestorage.com, or your MinIO.
Backup:S3:Region us-east-1 The region.
Backup:S3:Prefix shelf/ Where in the bucket the copies go. The newest Backup:Keep are kept there.
Backup:S3:PathStyle true with an endpoint Address the bucket as part of the path, as most S3-compatible services want.
Backup:S3:PartSizeMegabytes 64 Larger backups go up in parts of this size, at least 5.

Trash, history, and file checks

Setting Default What it does
Trash:Days 30 How long deleted books, quotes, sessions, and highlights wait in the trash, with their files, before they go for good.
History:Days 365 How long each change to a book's details, and each change to many books, is kept.
FileCheck:Days 7 How often every stored file is read through and checked. 0 turns the check off.

Calibre

Setting Default What it does
Calibre:Library none The Calibre library's folder, filled in on Open a Calibre library. See From Calibre.

Import folder

Setting Default What it does
Import:Folder none (off) A folder to watch. Each reader's books go in a folder inside it named after them; see From a folder. In Docker, mount one at /import and set this to /import.
Import:AfterImport move move puts added files into .imported; delete removes them, since Shelf keeps its own copy. Files it could not add are always moved to .not-added.
Import:EverySeconds 60 How often it looks.
Import:SettleSeconds 30 How long a file must have stayed unchanged before it is taken.

Scanned PDFs (OCR)

Setting Default What it does
Ocr:Enabled true Read the words of scanned pages, when Tesseract and Poppler are installed.
Ocr:Languages eng Tesseract's languages, such as eng+fra once tesseract-data-fra is installed.
Ocr:Resolution 200 The DPI pages are read at.
Ocr:Tesseract, Ocr:PdfToPpm, Ocr:PdfToText, Ocr:PdfInfo found on the path Where those tools are, if not on the path. Ocr:PdfInfo also reads a PDF's title and author when one is added from a file.

Books from elsewhere

Setting Default What it does
Comics:ComicVineKey none An API key from ComicVine, for Match on ComicVine on comics' pages. Without one, the button isn't shown.
Ebooks:Convert ebook-convert Calibre's converter, which turns Kindle files into EPUBs. Without it, Kindle files are refused with a note saying why.
FreeBooks:Enabled true The Free books page, which reaches gutenberg.org, librivox.org, and archive.org. Turn it off for a shelf with no internet.
AbsApi:Enabled true Let Audiobookshelf's phone apps sign in to Shelf. See Audiobookshelf's phone apps.
AbsSync:Enabled true Look once a day at the Audiobookshelf libraries readers keep in step with. See Keeping in step.
AbsSync:LookSeconds 30 How often Shelf checks whether a library is due its daily look, or was asked to look now.
Details:PauseMilliseconds 1200 The pause between books when details or covers are filled in for many, so Open Library and Google Books are asked politely.
Fetch:PollSeconds 20 How often Shelf asks the download client after the books it is fetching. The rest is set by an admin under Downloads; see Finding and fetching books.
Fetch:PauseMilliseconds 3000 The pause between books when looking for the whole want list, so the indexers are asked politely.
Kobo:Kepub true Send books to Kobo e-readers as KEPUBs, made from the EPUBs and kept beside them, for the Kobo's page numbers and statistics. false sends the EPUBs as they are.
Lookup:Dictionary true Look up in the reader, which sends the chosen words to Wiktionary and Wikipedia. Set false to keep them on the shelf.
Lookup:GoogleBooks true Ask Google Books when Open Library has no match or leaves fields blank. It sends the ISBN, or the title and author, to Google; set false to keep look-ups to Open Library alone.
Lookup:GoogleBooksKey none A Google Books API key (free, from the Google Cloud console). Without one, look-ups share Google's allowance for anonymous callers everywhere, which often runs out for the day; Shelf then carries on with Open Library alone.
Hardcover:LookupToken none A Hardcover API token that every reader's look-ups use, to fill a book's series and number and other blanks. Without it, only readers who connected Hardcover under Account ask it, with their own token.
AuthorWatch:Enabled true The daily look at Open Library for new books by the authors readers follow. Set false to stop it for everyone.
Finding:SearchUrl https://openlibrary.org/ Where Find new books and followed authors search: Open Library, or a mirror of its search API.
SeriesAlerts:Enabled true The daily look at Open Library for new books in a series, for readers who turn it on under Series. Set false to stop it for everyone.

Recaps

Setting Default What it does
Recap:ApiKey none An Anthropic API key, for Previously in this book. Without one, recaps are off.
Recap:Model claude-haiku-5-5 The model that writes recaps. A larger one writes better, for more.
Recap:Enabled true Turn recaps off without removing the key.

The text of the chapters a reader has read is sent to Anthropic to be summed up; see Previously in this book.

| Ask:UseModel | true | With a key set, lets Ask your shelf send the question alone to the model to understand it. false keeps questions on the server, read by simple rules. |

Money and updates

Setting Default What it does
Shelf:Currency the reader's language's The currency prices paid and got are shown in, as an ISO code such as EUR or USD.
Updates:Check true Ask GitHub twice a day whether a newer Shelf is out, and tell admins.
Updates:Url Shelf's releases on GitHub Where to ask, for a changed copy with releases of its own.

Read-along

Setting Default What it does
ReadAlong:Url none The transcriptions address of a speech-to-text service with OpenAI's API, such as http://speaches:8000/v1/audio/transcriptions. Without it, read-along is off. See Read-along.
ReadAlong:Model whisper-1 The model the service should use.
ReadAlong:ApiKey none Sent as a bearer token, for a service that asks for one.
ReadAlong:FFmpeg ffmpeg on the path Where ffmpeg is, to cut recordings into ten-minute pieces. Audio:FFmpeg sets it for making M4Bs too.
Audio:M4bBitrate 64k The AAC bitrate of an M4B made from tracks.
ReadAlong:MaxUploadMegabytes 24 for OpenAI, else none The largest file to send whole. A larger track needs ffmpeg.

Linked shelves

Setting Default What it does
Federation:Name Shelf at and the host of Email:PublicAddress The name linked shelves know this one by.

Linking needs Email:PublicAddress, as the other shelf calls this one at that address. See Linked shelves.

Notifications and webhooks

Setting Default What it does
Notices:Contact Email:From, or Shelf's project page Who the push services can contact about this shelf's notifications, as mailto:you@example.org or a web address. Apple asks for one.
Webhooks:Enabled true Readers' own webhooks. Webhooks post to addresses readers give, which can be on your own network (as Home Assistant usually is); set false if you don't want the shelf posting anywhere.
Notices:PushHosts none More push services to allow, by host name and separated by commas, for a browser whose service Shelf doesn't know. Shelf sends browser notifications only to Google's, Mozilla's, Apple's, and Microsoft's.

Links in ntfy and Gotify notifications need the shelf's own address, Email:PublicAddress; without it, they come without a link.

Logging

Shelf logs with ASP.NET Core's usual settings, such as Logging:LogLevel:Default. In Docker, docker logs shelf shows them.

Setting Default What it does
SourceUrl this project on GitHub Where the Source code link at the foot of each page goes. Shelf is under the AGPL: if you run a changed copy for other people, point this at your changed source.