Profile
Back to NewsBack
GitHub Trending 33 min
Reader Mode
JeremiahM37/librarr: Self-hosted book, audiobook, and manga search + download manager. 13 search sources, Torznab API, OPDS feed, request workflow, 4 download clients. The missing *arr for books.

JeremiahM37/librarr: Self-hosted book, audiobook, and manga search + download manager. 13 search sources, Torznab API, OPDS feed, request workflow, 4 download clients. The missing *arr for books.

Librarr

The missing *arr for books.

Build & Test Release Go License: MIT Docker image

Quick start · Features · Configuration · Releases · Contributing

Self-hosted book, audiobook, and manga search and download manager -- like Sonarr/Radarr but for your reading library.

Librarr searches all configured indexers in parallel, scores results by confidence, and auto-imports into your Calibre, Audiobookshelf, Kavita, or Komga library. Single ~17MB Go binary, no runtime dependencies — ~14MB RSS idle in a real homelab[^1], typically 10-20× lower than the .NET-based *arr apps.

[^1]: Measured on v1.1.0 in an LXC on Debian 12 (Mar 2026). Reference: Sonarr 4.x ≈ 240MB, Radarr 5.x ≈ 220MB on the same host.

Screenshots

| Search | Library | |---|---| | !Parallel indexer search with confidence scoring | !Unified library view across ebooks, audiobooks, and manga |

| Wanted | Settings | |---|---| | !Wanted list: every book has a state, a quality profile and the last scheduler decision | !Settings: scheduler, quality profile editor with cutoff, and monitored authors |

Quick Start

Docker (recommended)

services:
  librarr:
    image: ghcr.io/jeremiahm37/librarr:latest
    ports:
      - "5050:5050"
    volumes:
      - ./data:/data
      - /path/to/ebooks:/books/ebooks
      - /path/to/audiobooks:/books/audiobooks
      - /path/to/manga:/books/manga
    environment:
      - AUTH_USERNAME=admin
      - AUTH_PASSWORD=changeme
      - API_KEY=your-api-key-here
      - QB_URL=http://qbittorrent:8080
      - QB_USER=admin
      - QB_PASS=changeme
      - PROWLARR_URL=http://prowlarr:9696
      - PROWLARR_API_KEY=your-prowlarr-api-key
    restart: unless-stopped
docker compose up -d

Binary

# Download from releases
curl -LO https://github.com/JeremiahM37/librarr/releases/latest/download/librarr_linux_amd64.tar.gz
tar xzf librarr_linux_amd64.tar.gz

Configure

export AUTH_USERNAME=admin export AUTH_PASSWORD=changeme export QB_URL=http://localhost:8080

... set other env vars as needed

Run

./librarr

Open http://localhost:5050 in your browser.

Why Librarr?

  • Import your Goodreads or StoryGraph library via CSV and bulk-download everything
  • Request workflow -- users request books, admins approve, downloads happen automatically (like Jellyseerr for books)
  • Wanted list with quality profiles -- every wanted book is a monitored entity with a state (missing, downloading, upgrade wanted, satisfied); rank formats (EPUB > AZW3 > MOBI > PDF), set a cutoff, and librarr grabs a first copy, then upgrades it until the cutoff is met and retires the old file
  • Author monitoring that acts -- follow authors; new works (tracked by Open Library work key, so reissues don't fire) land on the wanted list automatically
  • Series auto-complete -- detects gaps in series and searches for missing volumes
  • Torznab API -- add Librarr as an indexer in Prowlarr or Readarr (it works both ways)
  • OPDS 1.2 feed -- browse your library from any e-reader app
  • Tiny footprint -- ~14MB idle RSS, runs comfortably on a Pi or any thermally-constrained mini-PC

Features

Search and Scoring

  • Pluggable indexer registry -- driver kinds listed below; default endpoints are fetched at startup from the librarr-sources companion repo and overrideable at runtime via LIBRARR_SOURCES_URL / LIBRARR_SOURCES_PATH
  • Confidence scoring -- 0-100 score with breakdown (title match, author match, format, seeders, file size)
  • Quality profiles -- per media type, best-first format ranking with a cutoff; formats left out are never grabbed automatically; built-in defaults for ebook / audiobook / manga
  • Release profiles -- preferred and excluded words for fine-grained filtering
  • Blocklist -- failed downloads are auto-blocked to prevent retries; a release that claimed a better format but delivered the same or a worse one is rejected after import and blocklisted; manual entries supported
  • Already-in-library detection -- results you already own are flagged with in_library / library_item_id and badged in the UI; matching normalizes punctuation, author prefixes and parenthetical alternate titles, so Agatha Christie - 4.50 From Paddington finds 4:50 from Paddington

Download Management

  • Multiple download clients -- qBittorrent, Transmission or rTorrent for torrents, plus SABnzbd for Usenet
  • Anna's Archive membership fast download -- optional account secret key uses /dyn/api/fast_download.json, with LibGen mirror fallback
  • Request/approval workflow -- pending, approved, searching, downloading, completed states with per-request notifications
  • Scheduled wanted-list searches -- the scheduler searches every monitored wanted item on an interval, runs its quality profile over the results (match score gates, format rank decides) and grabs the best acceptable release; POST /api/scheduler/run?wait=1 runs a pass synchronously
  • Torrent completion watcher -- polls download client, auto-imports completed downloads
  • Dead letter retry -- failed jobs can be retried individually or in bulk

Library Management

Without Audiobookshelf or Kavita configured, the library tabs show locally imported items with titles, authors, formats, and file sizes. Local library search matches titles and authors across all pages.

  • Auto-import pipeline -- organize files by author/title, rename on import (configurable pattern), scan into Calibre/Audiobookshelf/Kavita/Komga
  • Series auto-complete -- detect gaps in series, search for and download missing books
  • Author monitoring -- follow authors; the first check records their catalogue as a baseline, later checks add each new work to the wanted list (or only notify, per author)
  • Reading history -- track what you've read with stats (books per month, pages, completion rate)
  • Tags -- organize library items with custom tags for filtering and grouping
  • Series grouping -- groups related books/volumes in the library view
  • EPUB verification -- checks title word overlap to detect wrong-book downloads

Notifications and Webhooks

  • In-app notifications -- persistent alerts for downloads, requests, failures, and author releases
  • Discord webhooks -- rich embeds for download events, request updates, and errors
  • Generic webhooks -- JSON payloads for any webhook-compatible service
  • Configurable events -- choose which events trigger notifications

Import and Export

  • Goodreads CSV import -- import your shelves, auto-download "to-read" books
  • StoryGraph CSV import -- import your reading list
  • Library export -- JSON and CSV export for library, wishlist, and requests
  • Backup and restore -- full database backup with one-click restore

APIs and Integrations

  • Torznab/Newznab API at /torznab/api -- add as indexer in Prowlarr, Readarr, or any compatible app
  • OPDS 1.2 feed at /opds -- browse and download from e-readers (KOReader, Moon+ Reader, Librera)
  • Prometheus metrics at /metrics -- request counts, download stats, source health, library size
  • REST API -- full JSON API for all operations (see API section below)

Security and Multi-User

  • Multi-user auth -- session login with bcrypt passwords and admin/user roles
  • TOTP 2FA -- RFC 6238 time-based one-time passwords with QR code setup
  • OIDC / SSO -- OpenID Connect for Authelia, Keycloak, Authentik, and others
  • API key auth -- X-Api-Key header or ?apikey= parameter for programmatic access
  • Rate limiting -- per-endpoint rate limits with configurable thresholds
  • Security headers -- X-Content-Type-Options, X-Frame-Options, CORS, request size limits

UI and Admin

  • Modern dark UI -- Tailwind CSS, mobile-responsive, single-page app
  • Admin dashboard -- library stats, source health, activity log, system info
  • Bulk operations -- retry or cancel multiple downloads at once
  • File uploads -- drag and drop ebooks/audiobooks, auto-organize and library scan
  • Connection tests -- verify Prowlarr, qBittorrent, SABnzbd, Audiobookshelf, Kavita connectivity

Deployment

  • Single static binary -- ~17MB, zero CGO, pure-Go SQLite (modernc.org/sqlite)
  • Docker-ready -- minimal Alpine image, runs as non-root user
  • Cross-platform -- Linux, macOS, Windows; amd64, arm64, armv7

Search Sources

Librarr ships with driver implementations -- the protocols it can speak. The list of active indexers, their endpoints, mirrors, and enabled flags lives in a JSON registry, hosted in the librarr-sources companion repo and fetched at startup (similar to how Prowlarr syncs its indexer definitions). The binary itself ships no embedded registry. After the first successful fetch the registry is cached on disk so subsequent restarts work offline. To use a different registry, set LIBRARR_SOURCES_URL (another HTTP source) or LIBRARR_SOURCES_PATH (a local file).

| Driver | Used for | |--------|----------| | Torznab / Newznab | Prowlarr-managed indexers (any Torznab-compatible source) | | OPDS 1.2 catalogs | Library, archive, and OPDS-acquisition feeds | | Public JSON / RSS APIs | Open metadata APIs, public RSS feeds | | Direct-download with MD5/key lookup | Archive sites that key downloads by content hash | | Authenticated forum / tracker | Private trackers (user supplies credentials) | | Library-card-style API | Account-gated catalogs (user supplies endpoint + credentials) | | Web-novel crawler (lncrawl) | Web-novel sites with chapter pagination | | HTML scrape with regex extractor | Sites without a structured API |

To add or remove a specific indexer endpoint, edit the registry -- no code changes required.

Configuration

All configuration is via environment variables. Every variable has a sensible default.

Service URLs (ABS_URL, PROWLARR_URL, KAVITA_URL, …) may be written with or without a scheme — audiobookshelf:13378 is normalized to http://audiobookshelf:13378, and trailing slashes are trimmed. The same applies to URLs saved from the Settings UI. Use https:// explicitly when the service is behind TLS.

Server

| Variable | Default | Description | |----------|---------|-------------| | LIBRARR_PORT | 5050 | HTTP listen port | | LIBRARR_DB_PATH | /data/librarr.db | SQLite database path | | SETTINGS_FILE | /data/settings.json | Persistent settings file |

Authentication

| Variable | Default | Description | |----------|---------|-------------| | AUTH_USERNAME | | Login username (enables session auth) | | AUTH_PASSWORD | | Login password | | API_KEY | | API key for programmatic access (X-Api-Key header or ?apikey= param) |

OIDC / SSO

| Variable | Default | Description | |----------|---------|-------------| | OIDC_ENABLED | false | Enable OpenID Connect login | | OIDC_PROVIDER_NAME | SSO | Button label on login page | | OIDC_ISSUER | | OIDC issuer URL | | OIDC_CLIENT_ID | | OAuth2 client ID | | OIDC_CLIENT_SECRET | | OAuth2 client secret | | OIDC_REDIRECT_URI | | Callback URL (https://librarr.example.com/auth/oidc/callback) | | OIDC_AUTO_CREATE_USERS | true | Auto-create users on first OIDC login | | OIDC_DEFAULT_ROLE | user | Default role for OIDC-created users | | OIDC_PROXY_HEADERS_ENABLED | false | Trust Authentik identity headers from a reverse proxy | | OIDC_LINK_BY_USERNAME | false | Let an SSO login take over an existing non-admin password account with the same username (see below) | | LOCAL_AUTH_DISABLED | false | Send everyone to SSO; only local admins sign in with a password, at /login?local=1 | | OIDC_POST_LOGOUT_REDIRECT_URI | | Where the provider returns the browser after sign-out (e.g. https://librarr.example.com/). Must be registered with the provider | | OIDC_LOGOUT_URL | | Send SSO users here on sign-out instead of the provider's logout endpoint. Proxy-header setups need it, e.g. /outpost.goauthentik.io/sign_out | | LIBRARR_TRUSTED_PROXIES | | Comma-separated IPs/CIDRs of your reverse proxy. Required for OIDC_PROXY_HEADERS_ENABLED; also gates X-Forwarded-Proto |

When OIDC_PROXY_HEADERS_ENABLED=true and Librarr sits behind a trusted reverse proxy that injects Authentik headers like X-Authentik-Username, it will treat those requests as an authenticated SSO session, auto-provision the local user if needed, and skip the manual "Login with SSO" click. Enable this only for proxy-gated deployments. Identity headers are honored only when the connection comes from an address in LIBRARR_TRUSTED_PROXIES (e.g. LIBRARR_TRUSTED_PROXIES=172.18.0.5 or a Docker network CIDR). From any other peer they are ignored, and with the list empty they are always ignored — Librarr logs a warning at startup and users fall back to the "Login with SSO" button. Your proxy must also strip these headers from incoming client requests. Signing out of Librarr alone only clears its session cookie; while the proxy keeps sending the identity header, the next request signs the browser back in. Set OIDC_LOGOUT_URL to the proxy's sign-out URL (for Authentik, /outpost.goauthentik.io/sign_out) so signing out ends the proxy session too.

Account linking. Each account signs in one way: with a password (optionally with TOTP) or with SSO. SSO accounts are matched by the provider's subject (sub), so renaming a user at the provider doesn't split their account. Proxy-header logins carry only a username, so they match by username: renaming a user at the provider gives them a new account there.

  • A new SSO user gets a new account (with OIDC_AUTO_CREATE_USERS).
  • An existing password account is never taken over by an SSO login just
because the username matches. Its owner signs in with the password and links it under Settings > Account > Sign-in method. That works for admins too. Linking removes the password and TOTP, and ends the account's other sessions. E-readers using the OPDS feed sign in with that password, so they stop working; SSO accounts can't use OPDS.
  • OIDC_LINK_BY_USERNAME=true instead converts a non-admin password account
on the first SSO login with the same username, and binds the SSO identity to an account the proxy created (those know only a username). The username can come from claims a user may be able to edit at the provider (preferred_username, email, name), so turn it on only while migrating, then off. Admin accounts are never linked this way.
  • On a fresh install, register the admin account first: SSO sign-in is refused
until an admin exists.

Recovering an account. librarr reset-password sets a new password from the server's shell, for example docker exec -it librarr librarr reset-password admin. For an SSO account, add --unlink-sso to turn it back into a password account, for instance when the identity provider is down. The last admin can't be demoted or deleted.

Upgrading from a version without account linking. Accounts that SSO created earlier become password accounts with an unknown random password, so their SSO logins are refused until they are linked:

  1. If the admin account was created by SSO, run
librarr reset-password and sign in with that password. Link it to SSO under Settings > Account if you want.
  1. For the other users, either set OIDC_LINK_BY_USERNAME=true until they
have all signed in once with SSO, or set each a password under Users so they can sign in and link their own account.

SSO-only sign-in. With LOCAL_AUTH_DISABLED=true the login page goes straight to the identity provider. Password sign-in and registration are refused for everyone except local admins, who sign in at /login?local=1. The first-run admin account can still be registered. Librarr tries to reach the provider up to three times at startup, five seconds apart (configuration errors such as a wrong issuer are not retried). If it still can't, local sign-in stays enabled until the next restart, so you are not locked out. When the provider starts alongside Librarr, start it first (in Compose, depends_on with a health check). Non-admin password accounts can no longer sign in to link themselves, so have them link first, or migrate them with OIDC_LINK_BY_USERNAME.

The setting covers the web login only. Password accounts can still use the OPDS feed (/opds) with their password, so existing e-readers keep working. SSO accounts have no password, so they can't sign in to OPDS; e-readers can't go through an SSO login.

Signing out. Signing out of an SSO account also ends the session at the identity provider, when it advertises an end_session_endpoint (Authentik, Keycloak, Authelia and most others do). Set OIDC_POST_LOGOUT_REDIRECT_URI to come back to Librarr afterwards; without it, the provider shows its own signed-out page. OIDC_LOGOUT_URL overrides the provider's endpoint. Local admins signing out stay in Librarr. After signing out, the login page waits for a click instead of redirecting to the provider, so a provider that couldn't end its session doesn't silently sign you back in.

Download Clients

Librarr sends torrents to qBittorrent, Transmission or rTorrent. Configure one, or configure both and choose with TORRENT_CLIENT. The category/save-path settings below apply to all of them (Transmission uses the category as a torrent label, which requires Transmission 3.0+; rTorrent stores it in d.custom1, shown as the label in ruTorrent).

rTorrent notes. Downloads go to RTORRENT_DIRECTORY, or rTorrent's own directory.default when it is empty; QB_*_SAVE_PATH is not used. Each torrent's path is read from rTorrent at import time, so finished downloads can be moved, but the folder a torrent finishes in must be mounted in Librarr at INCOMING_DIR (or QB_AUDIOBOOK_SAVE_PATH / MANGA_INCOMING_DIR). To keep seeding, set REMOVE_TORRENT_AFTER_IMPORT=false with IMPORT_MODE=hardlink. rTorrent's API cannot delete data, so removing a torrent with its files deletes them through that mount.

| Variable | Default | Description | |----------|---------|-------------| | TORRENT_CLIENT | | Active torrent backend when both are configured: empty (auto, qBittorrent preferred), qbittorrent, transmission, or rtorrent | | QB_URL | | qBittorrent Web UI URL | | QB_USER | admin | qBittorrent username | | QB_PASS | | qBittorrent password | | QB_SAVE_PATH | /downloads | Ebook download path as seen by qBittorrent (the remote/client-side path) | | QB_CATEGORY | librarr | Torrent category for ebooks | | QB_AUDIOBOOK_SAVE_PATH | /audiobooks-incoming | Audiobook download path | | QB_AUDIOBOOK_CATEGORY | audiobooks | Torrent category for audiobooks | | QB_MANGA_SAVE_PATH | /manga-incoming | Manga download path | | QB_MANGA_CATEGORY | manga | Torrent category for manga | | QB_PRIORITY | 1 | Download client priority (lower = preferred) | | REMOVE_TORRENT_AFTER_IMPORT | true | Remove the torrent from the download client after a successful import. Set it to false to keep seeding — librarr then hardlinks imports instead of moving them, so the payload stays where the client can seed it. See Seeding after import. | | TRANSMISSION_URL | | Transmission RPC URL (e.g. http://transmission:9091) | | TRANSMISSION_USER | | Transmission RPC username (optional — only if RPC auth is enabled) | | TRANSMISSION_PASS | | Transmission RPC password (optional) | | RTORRENT_URL | | rTorrent XML-RPC URL over HTTP, e.g. http://rutorrent/RPC2 (no path means /RPC2) | | RTORRENT_USER | | HTTP basic auth username (optional) | | RTORRENT_PASS | | HTTP basic auth password (optional) | | RTORRENT_DIRECTORY | | Download directory (optional; empty uses rTorrent's directory.default) | | SABNZBD_URL | | SABnzbd URL | | SABNZBD_API_KEY | | SABnzbd API key | | SABNZBD_CATEGORY | librarr | NZB download category | | SAB_PRIORITY | 2 | Download client priority |

Prowlarr

| Variable | Default | Description | |----------|---------|-------------| | PROWLARR_URL | | Prowlarr URL | | PROWLARR_API_KEY | | Prowlarr API key |

Anna's Archive

Search is currently blocked upstream (August 2026). Anna's Archive sits
behind DDoS-Guard, which serves an hCaptcha "I am human" check on
/search and /md5/*. This blocks a real browser, not just scrapers, so the
annas source returns no results and will report itself unhealthy. The cause
is upstream and legal rather than a librarr bug: after the January 2026
lawsuit, Cloudflare disabled the nameservers for several AA domains and the
survivors moved to DDoS-Guard, followed by a global domain takedown order.
> What this means in practice:
> - Search: unavailable. Every surviving domain (.gl, .pk, .gd)
behaves the same, and AA publishes no JSON search API — its own API
self-documentation (GET /dyn/api/fast_download.json with no parameters)
lists fast_download as the only stable endpoint.
- Downloads: still work with a membership key. /dyn/api/fast_download.json
is not behind the challenge, so ANNAS_ARCHIVE_SECRET_KEY below still
resolves MD5s to download URLs.
- FlareSolverr does not help. It solves Cloudflare, not DDoS-Guard, and
times out on the hCaptcha challenge.
- annas-archive.org and .se no longer resolve, and annas-archive.li is
an unrelated parked domain — do not point ANNAS_ARCHIVE_DOMAIN at it.
> Other sources (Prowlarr, Gutenberg, Open Library, Standard Ebooks, Z-Library)
are unaffected.

| Variable | Default | Description | |----------|---------|-------------| | ANNAS_ARCHIVE_DOMAIN | (from sources registry) | AA mirror hostname (no scheme) | | ANNAS_ARCHIVE_SECRET_KEY | | Account secret key from the AA login page (also called a donator key in some tools). Used for /dyn/api/fast_download.json when the account has an active membership. Env alias AA_DONATOR_KEY is still accepted. Without a key, downloads use public LibGen mirrors. |

Library Imports

| Variable | Default | Description | |----------|---------|-------------| | CALIBRE_LIBRARY_PATH | | Path to Calibre library (auto-import via calibredb) | | CALIBRE_URL | | Calibre-Web URL | | KAVITA_URL | | Kavita server URL | | KAVITA_USER | | Kavita username | | KAVITA_PASS | | Kavita password | | KAVITA_API_KEY | | Kavita auth key (Kavita 0.8.9+), used instead of KAVITA_USER and KAVITA_PASS. Library scans need a key from an admin account | | KAVITA_LIBRARY_PATH | | Kavita ebook library path | | KAVITA_MANGA_LIBRARY_PATH | | Kavita manga library path | | KAVITA_EBOOK_LIBRARY_ID | | Kavita library to scan after an ebook import (blank = all) | | KAVITA_MANGA_LIBRARY_ID | | Kavita library to scan after a manga import (blank = all) | | KAVITA_PUBLIC_URL | | Kavita URL for external links | | ABS_URL | | Audiobookshelf server URL | | ABS_TOKEN | | Audiobookshelf API token | | ABS_LIBRARY_ID | | Audiobookshelf audiobook library ID | | ABS_EBOOK_LIBRARY_ID | | Audiobookshelf ebook library ID | | ABS_PUBLIC_URL | | Audiobookshelf URL for external links | | KOMGA_URL | | Komga server URL | | KOMGA_USER | | Komga username | | KOMGA_PASS | | Komga password | | KOMGA_LIBRARY_ID | | Komga library ID | | KOMGA_LIBRARY_PATH | | Komga library path |

File Organization

| Variable | Default | Description | |----------|---------|-------------| | FILE_ORG_ENABLED | true | Auto-organize downloaded files | | IMPORT_MODE | (automatic) | How organized files reach the library: move (the download payload is consumed), hardlink (a second name for the same data — keeps seeding, no extra disk, same filesystem only), or copy (keeps seeding, uses twice the disk). Leave it unset and it follows REMOVE_TORRENT_AFTER_IMPORT: removing torrents moves, keeping them hardlinks. See Seeding after import. | | EBOOK_DIR | /books/ebooks | Organized ebook destination | | AUDIOBOOK_DIR | /books/audiobooks | Organized audiobook destination | | MANGA_DIR | /books/manga | Organized manga destination | | INCOMING_DIR | /data/incoming | Incoming file staging directory as seen by Librarr; qBittorrent paths beneath QB_SAVE_PATH are translated here before import | | MANGA_INCOMING_DIR | /data/manga-incoming | Manga incoming staging directory |

Seeding after import

One setting. For private trackers with seed-time minimums:

REMOVE_TORRENT_AFTER_IMPORT=false

Two separate things have to survive an import for a torrent to keep seeding — its record in the client, and its payload files where the client wrote them. Keeping the record is what that setting says; keeping the payload is what IMPORT_MODE controls. Rather than make you set both, librarr infers the second from the first:

| REMOVE_TORRENT_AFTER_IMPORT | IMPORT_MODE unset resolves to | Result | |---|---|---| | true (default) | move | Payload goes into the library; nothing is left to seed, and nothing needs to be. Unchanged from earlier releases. | | false | hardlink | Payload stays in the download folder, the library gets a second name for the same data, and the torrent keeps seeding. |

Setting IMPORT_MODE explicitly always wins over that inference — use it to force copy on a filesystem without hardlinks, or hardlink while still removing torrents. IMPORT_MODE=move together with REMOVE_TORRENT_AFTER_IMPORT=false is the one combination that cannot seed; librarr logs a warning at startup if you configure it.

Notes on the modes:

  • hardlink costs no extra disk — the library entry and the download folder
are two names for the same data, and the space is reclaimed when both are gone. It requires the download folder and the library directories to be on the same filesystem; in Docker that usually means one volume mounted at a common parent (e.g. /data) rather than separate /downloads and /books mounts. When a link cannot be created (different filesystem, or a filesystem without hardlinks such as CIFS/exFAT), librarr logs a warning and copies instead, so the import still lands.
  • copy always works but stores the book twice until you delete the torrent.
It is also the mode to pick when something else writes to your library files: a hardlinked file is the seeded data, so a tool that rewrites tags in place (some audiobook taggers do) changes the torrent's bytes underneath it and the next recheck fails. Tools that write a new file and rename it are safe, as is librarr itself — it only ever reads imported files or copies them onward.
  • With hardlink or copy, if you also leave REMOVE_TORRENT_AFTER_IMPORT=true,
librarr removes the torrent and its files once every file is safely in the library — otherwise the payload would sit in the download folder with nothing left to clean it up. The library copy is unaffected (a hardlink keeps the data alive). If any file failed to organize, the payload is left alone.
  • Seed goals belong to your torrent client, not to librarr. Once imports
hardlink, qBittorrent's own share-ratio / seeding-time limits can be set to "remove torrent and its files" when the goal is met: the download folder is cleaned up on the tracker's schedule and the library keeps the book, because the library entry is a link to the same data rather than a second copy.
  • File organization must be on (FILE_ORG_ENABLED=true) for any of this; with it
off, librarr indexes files where they already are and never touches them.

Scope. IMPORT_MODE applies to torrent imports and manual imports — the cases where the source may be a live torrent payload. Everything else always moves, because nothing is seeding it and leaving a second copy behind would just accumulate: SABnzbd/usenet imports (librarr never removes anything from the completed folder), web UI uploads, and direct HTTP downloads from sources like Anna's Archive, all of which are librarr's own files.

Sources Registry

| Variable | Default | Description | |----------|---------|-------------| | LIBRARR_SOURCES_URL | (built-in default points at librarr-sources) | URL of a JSON sources registry, fetched at startup | | LIBRARR_SOURCES_PATH | | Local path to a sources registry JSON file; takes precedence over URL |

Resolution order on startup: LIBRARR_SOURCES_PATH → LIBRARR_SOURCES_URL → built-in default URL → on-disk cache (/sources-cache.json) → empty registry. Successful URL fetches are cached so subsequent restarts work offline. If you set LIBRARR_SOURCES_URL, your value replaces the default — it does not stack on top of it.

Legacy per-source env vars (e.g. PROWLARR_URL and other per-driver overrides) continue to be honored and override values loaded from the registry.

Search / Downloads

| Variable | Default | Description | |----------|---------|-------------| | MIN_TORRENT_SIZE_BYTES | 10000 | Minimum torrent size filter (10 KB) | | MAX_TORRENT_SIZE_BYTES | 2000000000 | Maximum torrent size filter (2 GB) | | MAX_RETRIES | 2 | Download retry attempts | | RETRY_BACKOFF_SECONDS | 60 | Seconds between retries | | CIRCUIT_BREAKER_THRESHOLD | 3 | Failures before disabling a source | | CIRCUIT_BREAKER_TIMEOUT | 300 | Seconds before re-enabling a tripped source |

Feature Toggles

| Variable | Default | Description | |----------|---------|-------------| | RATE_LIMIT_ENABLED | true | Per-source rate limiting | | METRICS_ENABLED | true | Prometheus metrics endpoint | | WEBNOVEL_ENABLED | true | Web novel search (requires lncrawl container) | | WISHLIST_CLEANUP_ENABLED | false | Periodically remove wishlist items already found in the library | | WISHLIST_CLEANUP_INTERVAL_HOURS | 12 | Hours between wishlist cleanup scans | | WISHLIST_CLEANUP_DRY_RUN | true | Log conservative wishlist cleanup matches without deleting | | MANGADEX_ENABLED | true | MangaDex search | | AUTHOR_MONITOR_ENABLED | false | Background author monitoring | | AUTHOR_CHECK_INTERVAL_DAYS | 7 | Default check interval for newly followed authors | | AUTHOR_MONITOR_AUTO_ADD | true | Put newly found works on the wanted list (otherwise only notify) | | OPENLIBRARY_URL | https://openlibrary.org | Open Library origin used by the author monitor |

Wanted List & Quality Profiles

These are also editable at runtime under Settings → Wanted List & Quality; values saved there persist in settings.json and override the environment.

| Variable | Default | Description | |----------|---------|-------------| | SCHEDULER_ENABLED | false | Search monitored wanted items in the background | | SCHEDULER_INTERVAL_HOURS | 24 | Hours between passes | | SCHEDULER_AUTO_DOWNLOAD | false | Grab the best acceptable release (otherwise only notify) | | SCHEDULER_MIN_SCORE | 70 | Minimum match confidence (0-100); the profile ranks what passes | | SCHEDULER_ITEM_DELAY_SECONDS | 5 | Pause between two items that hit the sources | | AUTO_UPGRADE_ENABLED | false | Global switch: replace an existing file when a better-ranked format appears (each profile also has its own flag) | | UPGRADE_KEEP_OLD_FILES | false | Keep the superseded file and its library row after an upgrade instead of deleting it |

Torznab

| Variable | Default | Description | |----------|---------|-------------| | TORZNAB_API_KEY | | API key for the Torznab endpoint |

API Endpoints

Authentication

| Method | Path | Description | |--------|------|-------------| | POST | /api/login | Session login | | POST | /api/login/totp | TOTP 2FA verification | | POST | /api/register | Register new user | | POST | /api/logout | End session | | GET | /api/auth/status | Current auth state |

User Management (admin)

| Method | Path | Description | |--------|------|-------------| | GET | /api/users | List all users | | PATCH | /api/users/{id} | Update user role/status | | DELETE | /api/users/{id} | Delete user |

TOTP 2FA

| Method | Path | Description | |--------|------|-------------| | POST | /api/totp/setup | Generate TOTP secret + QR code | | POST | /api/totp/verify | Verify and enable TOTP | | POST | /api/totp/disable | Disable TOTP | | GET | /api/totp/status | Check if TOTP is enabled |

Search

| Method | Path | Description | |--------|------|-------------| | GET | /api/search?q= | Search ebooks across all sources | | GET | /api/search/audiobooks?q= | Search audiobooks | | GET | /api/search/manga?q= | Search manga |

Every result carries in_library (always present), plus library_item_id and library_title when the book is already in your library.

Downloads

| Method | Path | Description | |--------|------|-------------| | POST | /api/download | Download a direct-download result | | POST | /api/download/torrent | Download a torrent result | | POST | /api/download/annas | Download via content-hash (MD5/key) lookup | | POST | /api/download/audiobook | Download an audiobook | | GET | /api/downloads | List active/completed downloads | | DELETE | /api/downloads/torrent/{hash} | Remove a torrent download | | DELETE | /api/downloads/novel/{jobID} | Remove a novel download job | | POST | /api/downloads/clear | Clear finished downloads | | POST | /api/downloads/jobs/{id}/retry | Retry a failed download |

All four download endpoints refuse a book that is already in your library with 409 Conflict and {"code": "already_in_library", "library_item_id": N}. Set "force": true on the request to download it anyway (a different edition, a different format); that is what the UI's Download anyway button sends.

Library

| Method | Path | Description | |--------|------|-------------| | GET | /api/library | List ebooks in library | | GET | /api/library/audiobooks | List audiobooks | | GET | /api/library/manga | List manga | | DELETE | /api/library/book/{id} | Remove ebook | | DELETE | /api/library/audiobook/{id} | Remove audiobook | | DELETE | /api/library/manga/{id} | Remove a local manga library record | | GET | /api/stats | Library statistics | | GET | /api/activity | Recent activity log |

Removing a local library item preserves the file on disk and returns linked wanted entries to missing. Local ebooks, audiobooks, and manga all offer a confirmation before removal. Manga displayed from Kavita is managed through the Open in Kavita link; its series IDs are not Librarr item IDs. If Kavita is configured, API callers removing a local manga record must explicitly pass ?source=local with the Librarr item ID.

Requests

| Method | Path | Description | |--------|------|-------------| | POST | /api/requests | Create a book request | | GET | /api/requests | List all requests | | GET | /api/requests/{id} | Get request details | | PUT | /api/requests/{id}/approve | Approve request (admin) | | PUT | /api/requests/{id}/cancel | Cancel request | | PUT | /api/requests/{id}/retry | Retry failed request (admin) | | PUT | /api/requests/{id}/select | Select search result (admin) | | DELETE | /api/requests/{id} | Delete request (admin) |

Wishlist

| Method | Path | Description | |--------|------|-------------| | GET | /api/wishlist | List wanted items with derived state, current_format, profile_name, cutoff_met | | POST | /api/wishlist | Add a wanted item (title, author, media_type, quality_profile_id, monitored) | | PATCH | /api/wishlist/{id} | Change monitored / quality_profile_id | | POST | /api/wishlist/{id}/search | Run the profile decision now (admin); {"dry_run": true} explains every candidate without grabbing | | DELETE | /api/wishlist/{id} | Remove from the wanted list |

A wanted item is never deleted by a grab. It remembers the library file that satisfies it; the scheduler skips it once the file meets the profile's cutoff (or when upgrades are off) and otherwise keeps looking for a strictly better format. Deleting the library file sends the item back to missing; files that arrive by other routes are linked on the next pass by title and author.

Notifications

| Method | Path | Description | |--------|------|-------------| | GET | /api/notifications | List notifications | | GET | /api/notifications/unread | Unread count | | PUT | /api/notifications/{id}/read | Mark as read | | PUT | /api/notifications/read-all | Mark all as read | | DELETE | /api/notifications/{id} | Delete notification |

Quality Profiles

| Method | Path | Description | |--------|------|-------------| | GET | /api/quality-profiles | List quality profiles | | GET | /api/quality-profiles/default | Built-in profile per media type + global upgrade switches | | GET | /api/quality-profiles/formats | Known formats per media type (for editors) | | POST | /api/quality-profiles | Create profile (admin) | | PUT | /api/quality-profiles/{id} | Update profile (admin) | | DELETE | /api/quality-profiles/{id} | Delete profile (admin) |

Release Profiles

| Method | Path | Description | |--------|------|-------------| | GET | /api/release-profiles | List release profiles | | POST | /api/release-profiles | Create profile (admin) | | PUT | /api/release-profiles/{id} | Update profile (admin) | | DELETE | /api/release-profiles/{id} | Delete profile (admin) |

Blocklist

| Method | Path | Description | |--------|------|-------------| | GET | /api/blocklist | List blocked items | | POST | /api/blocklist | Add entry (admin) | | DELETE | /api/blocklist/{id} | Remove entry (admin) | | POST | /api/blocklist/clear | Clear all (admin) |

Series

| Method | Path | Description | |--------|------|-------------| | GET | /api/series | List detected series | | GET | /api/series/{name}/missing | Find missing volumes | | POST | /api/series/{name}/search-missing | Search for missing volumes |

Authors

| Method | Path | Description | |--------|------|-------------| | GET | /api/authors | List monitored authors | | POST | /api/authors/monitor | Add author (admin) | | DELETE | /api/authors/{id} | Remove author (admin) | | PATCH | /api/authors/{id} | Change check interval / auto-add (admin) | | POST | /api/authors/{id}/check | Check one author now (admin); first check records a baseline |

Reading History

| Method | Path | Description | |--------|------|-------------| | POST | /api/history | Add history entry | | GET | /api/history | Get reading history | | PATCH | /api/history/{id} | Update entry | | DELETE | /api/history/{id} | Delete entry | | GET | /api/history/stats | Reading statistics |

Tags

| Method | Path | Description | |--------|------|-------------| | GET | /api/tags | List all tags | | POST | /api/tags | Create tag | | DELETE | /api/tags/{id} | Delete tag | | GET | /api/library/{id}/tags | Get item tags | | POST | /api/library/{id}/tags | Add tags to item | | DELETE | /api/library/{id}/tags/{tagId} | Remove tag from item |

Import / Export

| Method | Path | Description | |--------|------|-------------| | POST | /api/import/csv | Bulk import from CSV | | POST | /api/import/goodreads | Import Goodreads CSV | | POST | /api/import/storygraph | Import StoryGraph CSV | | POST | /api/import/library | Import library JSON | | POST | /api/import/wishlist | Import wishlist JSON | | POST | /api/import/scan | Scan directory for files | | POST | /api/import/files | Import scanned files | | GET | /api/export/library | Export library as JSON | | GET | /api/export/wishlist | Export wishlist as JSON | | GET | /api/export/requests | Export requests as JSON |

Backup / Restore

| Method | Path | Description | |--------|------|-------------| | POST | /api/backup/create | Create backup | | GET | /api/backup | Download latest backup | | GET | /api/backup/list | List backups | | POST | /api/restore | Restore from backup |

Scheduler

| Method | Path | Description | |--------|------|-------------| | GET | /api/scheduler/status | Scheduler status and next run | | POST | /api/scheduler/run | Trigger a pass (admin); ?wait=1 runs it synchronously and returns the statistics | | PUT | /api/scheduler/config | Update and persist scheduler / upgrade settings (admin) |

Webhooks (admin)

| Method | Path | Description | |--------|------|-------------| | GET | /api/webhooks | List webhook configs | | POST | /api/webhooks | Create webhook | | DELETE | /api/webhooks/{id} | Delete webhook | | POST | /api/webhooks/test | Test webhook delivery |

Admin

| Method | Path | Description | |--------|------|-------------| | GET | /api/admin/dashboard | Dashboard stats | | GET | /api/admin/activity | Admin activity log | | GET | /api/admin/health | Source and system health | | POST | /api/admin/bulk/retry | Bulk retry downloads | | POST | /api/admin/bulk/cancel | Bulk cancel downloads |

Connection Tests (admin)

| Method | Path | Description | |--------|------|-------------| | POST | /api/test/prowlarr | Test Prowlarr connection | | POST | /api/test/qbittorrent | Test qBittorrent connection | | POST | /api/test/transmission | Test Transmission connection | | POST | /api/test/rtorrent | Test rTorrent connection | | POST | /api/test/audiobookshelf | Test Audiobookshelf connection | | POST | /api/test/kavita | Test Kavita connection | | POST | /api/test/sabnzbd | Test SABnzbd connection |

System

| Method | Path | Description | |--------|------|-------------| | GET | /health | Health check | | GET | /metrics | Prometheus metrics |

Torznab / Newznab API

Librarr exposes a standard Torznab API at /torznab/api that can be added as an indexer in Prowlarr, Readarr, or any Torznab-compatible application.

Setup in Prowlarr / Readarr:

  1. Go to Settings > Indexers > Add
  2. Select "Generic Torznab" (or "Generic Newznab")
  3. Set the URL to http://your-librarr-host:5050/torznab/api
  4. Set the API Key to your TORZNAB_API_KEY value
  5. Test and save
Capabilities: GET /torznab/api?t=caps returns the supported search categories and capabilities.

OPDS Feed

Librarr serves an OPDS 1.2 catalog at /opds for e-reader apps (KOReader, Moon+ Reader, Librera, etc.).

| Path | Description | |------|-------------| | /opds | Catalog root | | /opds/books | Browse all books | | /opds/search?q= | Search the catalog | | /opds/download/{id} | Download a book file | | /opds/opensearch.xml | OpenSearch descriptor |

Setup: Add http://your-librarr-host:5050/opds as an OPDS catalog in your e-reader. If auth is enabled, enter your Librarr username and password.

When auth is enabled every /opds route requires credentials (HTTP Basic, a browser session, or the API key). Accounts with two-factor auth enabled, and SSO-only accounts, cannot use their password here: enter the API_KEY value as the password instead (any username). Repeated failed logins from one address are temporarily blocked. Basic auth sends the password with every request, so use HTTPS when the feed is reachable beyond your LAN.

Using Librarr with Claude / MCP

Librarr's REST API is the integration surface, so any Model Context Protocol server can expose Librarr's search, download, and library tools to an LLM (Claude Desktop, Claude Code, Open WebUI, Cursor, etc.). There's no built-in MCP server in Librarr — you wire it into the MCP server you already run.

Auth: every endpoint accepts an X-Api-Key header or ?apikey= query param. Set API_KEY in your docker-compose.yml and reuse it from your MCP server.

Minimal Python example using fastmcp:

# librarr_mcp.py — run as: fastmcp run librarr_mcp.py
import os, httpx
from fastmcp import FastMCP

LIBRARR = os.environ["LIBRARR_URL"] # e.g. http://librarr.lan:5050 API_KEY = os.environ["LIBRARR_API_KEY"] HEADERS = {"X-Api-Key": API_KEY}

mcp = FastMCP("librarr")

@mcp.tool() async def search_books(query: str) -> list[dict]: """Search for an ebook across all configured sources.""" async with httpx.AsyncClient() as c: r = await c.get(f"{LIBRARR}/api/search", params={"q": query}, headers=HEADERS) return r.json()

@mcp.tool() async def download_book(result: dict) -> dict: """Queue an ebook download. Pass a result object returned by search_books — it already contains source, title, and the source-specific URL field (download_url / magnet_url / md5 / etc.).""" async with httpx.AsyncClient() as c: r = await c.post(f"{LIBRARR}/api/download", json=result, headers=HEADERS) return r.json()

@mcp.tool() async def list_downloads() -> list[dict]: """List active and recent download jobs with status.""" async with httpx.AsyncClient() as c: r = await c.get(f"{LIBRARR}/api/downloads", headers=HEADERS) return r.json()

Register with Claude Desktop / Claude Code (~/.claude.json or claude_desktop_config.json):

{
  "mcpServers": {
    "librarr": {
      "command": "fastmcp",
      "args": ["run", "/path/to/librarr_mcp.py"],
      "env": {
        "LIBRARR_URL": "http://librarr.lan:5050",
        "LIBRARR_API_KEY": "your-api-key-here"
      }
    }
  }
}

The same pattern works for audiobooks (/api/search/audiobooks, /api/download/audiobook) and manga (/api/search/manga, /api/download/torrent). Wrap whichever subset of the API Endpoints above is useful to your assistant.

Architecture

Single static binary, zero CGO dependencies, pure-Go SQLite via modernc.org/sqlite.

cmd/librarr/main.go            Entry point
internal/
  config/config.go              Env var configuration
  db/                           SQLite persistence + migrations
  models/                       Core types (books, downloads, wishlist, requests, etc.)
  api/                          HTTP handlers, router, middleware
    auth.go                     Session auth + bcrypt
    totp.go                     TOTP 2FA (RFC 6238)
    oidc.go                     OpenID Connect / SSO
    search.go                   Search endpoint handlers
    download.go                 Download management
    library.go                  Library CRUD
    requests.go                 Request workflow
    notifications.go            In-app notifications
    qualityprofile.go           Quality profiles
    releaseprofile.go           Release profiles
    blocklist.go                Blocklist management
    tags.go                     Tag management
    history.go                  Reading history
    series.go                   Series detection + auto-complete
    importexport.go             Import/export (JSON, CSV, Goodreads, StoryGraph)
    backup.go                   Database backup/restore
    webhook.go                  Webhook configuration
    opds.go                     OPDS 1.2 feed
    admin.go                    Admin dashboard
    metrics.go                  Prometheus metrics
    csv.go                      CSV bulk import
    ratelimit.go                Per-source rate limiting
    router.go                   Route registration
  search/                       Search source implementations
  download/                     Download manager (qBit, Transmission, rTorrent, Deluge, SABnzbd)
  organize/                     Post-download file organization + library import
  metadata/                     Open Library metadata enrichment
  scheduler/                    Background scheduler, series detector, author monitor
  webhook/                      Webhook sender (Discord + generic)
  torznab/                      Torznab/Newznab API handler
web/
  index.html                    Single-page web UI (Tailwind CSS)
Dockerfile                      Multi-stage Alpine build
.goreleaser.yml                 Cross-platform release builds

License

MIT

Disclaimer

This software is provided for educational and personal use only. Users are responsible for ensuring their use complies with all applicable laws and regulations in their jurisdiction. The developers do not condone or encourage copyright infringement or any illegal activity. This tool does not host, store, or distribute any copyrighted content, a

... (README truncated for length)

Chat with me