zotero-cli
Your Zotero library, from the command line.
zotero-cli lets you read, organize and change a Zotero library without opening the Zotero app. Every operation is a plain command, so you can put it in shell scripts, cron jobs and CI, or hand it to an LLM agent as a tool.
It talks to the Zotero Web API (personal and group libraries). It can also read your local zotero.sqlite offline.
Why a CLI for Zotero?
- Scriptable: items, collections, tags, notes and attachments are all reachable from a terminal, with no GUI steps in between.
- Machine-readable output:
item listprintsjson,csvormarkdown, and--fieldspicks the columns. Data goes to stdout and messages to stderr, so you can pipe it intojq, a spreadsheet or a prompt. - Suited to LLM agents: an agent can run
--helpon any command to learn its options, and most commands' help includes worked examples.item export --as mdextracts the text of an item's PDF into a.mdfile an LLM can read. - Preview first:
collection clean,collection delete --recursive,slr prune,slr load,tag purge,item merge,item hydrate,item pdf stripandslr dedupeonly show what they would do until you add--execute. A recursive delete also asks for confirmation (--yesin scripts), and a collection name that matches several collections is refused rather than guessed.item purgeandcollection purgeask for confirmation.item delete,storage checkoutandsystem restorepredate this policy and still apply immediately without a flag (a deprecation warning says so); pass their--execute, or--dry-runto preview first - this changes to preview-by-default in 4.0.--offlinereads the local database and is read-only apart from trash/restore. - Runs anywhere: a single binary for Linux and Windows, no Python needed. Also available as a container or a Python package.
What it can do
Library management
- Items: list, inspect, add, update, move, merge duplicates, trash/restore, delete, and copy between libraries.
- Collections: create, rename, nest, empty, delete, and export whole collections (BibTeX, RIS, Markdown, or a formatted bibliography in any CSL style).
- Tags: list, add, and bulk-remove tags across a collection.
- Search: by DOI or title substring.
- Attachments: fetch missing PDFs from open-access sources, attach local files, strip attachments, and move stored files out to local or network storage while keeping them linked (
storage checkout).
Import and metadata
- Import from arXiv, DOI, BibTeX/RIS/CSV files, the Brazilian BDTD thesis repository, or manual entry.
- Metadata lookup from Semantic Scholar, CrossRef, OpenAlex, PubMed, Unpaywall and more when importing by DOI, and
item hydrateto fill in empty fields (abstract, venue, date, authors, DOI) for items you already have, including published versions of arXiv preprints. - Library health reports: duplicates, missing PDFs, DOIs or abstracts, disk usage, and checking the citations in a LaTeX manuscript against the library.
Operations
- Backup and restore the whole library, or one collection, as a compressed
.zafarchive, attachments included. system checktests the connection to every service you've configured (Zotero and the metadata providers) in one go.- Local HTTP API (
serve): read-only endpoints for items, collections and background jobs, for local scripts and dashboards.
Optional: literature review toolkit
- Systematic literature review (
slr): screening decisions recorded as auditable Zotero notes, PRISMA statistics, citation snowballing and data extraction. See docs/commands/slr.md.
🍳 Cookbook
Get a collection as JSON
zotero-cli item list --collection "Reading List" --wide --format json | jq '.[] | {title, year, doi}'
Choose exactly the columns you want
zotero-cli item list --collection "Reading List" --fields key,first_author,year,venue,DOI --format csv > reading.csv
Add papers from a script
for doi in 10.1145/3290605.3300233 10.1038/nature14539; do
zotero-cli import doi "$doi" --collection "Inbox"
done
zotero-cli item pdf fetch --collection "Inbox"
Hand a paper to an LLM
# The text of the item's PDF, saved as .md
zotero-cli item export --key ABCD1234 --as md --output ./context/
Clean up tags, previewing before you change anything
zotero-cli tag purge --collection "Old Project" # preview only
zotero-cli tag purge --collection "Old Project" --execute # apply
Query your library without internet access
# Reads the local zotero.sqlite (set database_path in config.toml)
zotero-cli --offline item list --collection "Reading List" --format markdown
Back up everything
zotero-cli system backup --output library_2026-09.zaf
zotero-cli system restore --file library_2026-09.zaf --dry-run
Try it without touching your real library
zotero-cli system check # is every configured service reachable?
zotero-cli system demo-sandbox # disposable collection of sample papers
zotero-cli system demo-sandbox --clean
📚 Command Reference
| Noun | Description | Key Verbs |
|---|---|---|
init |
Config wizard | (default) |
item |
Items | list, inspect, annotations, add, update, move, merge, export, pdf, hydrate, purge, delete, trash, restore, transfer |
collection |
Folders | list, create, rename, delete, clean, export, backup, purge |
tag |
Tags | list, add, purge |
search |
Finder | --doi, --title |
import |
Ingest | arxiv, doi, file, bdtd, manual |
report |
Library health | duplicates, audit, stats, attachments, verify-latex |
storage |
Attachments | checkout |
system |
Operations | info, check, selftest, groups, switch, backup, verify, restore, normalize, demo-sandbox, jobs |
serve |
Local HTTP API | (default) |
schema |
The command line as JSON, for scripts and agents | (default) |
mcp |
Read-only MCP server for AI clients (optional mcp extra) |
serve |
slr |
Literature review | source, screen, decide, load, list, reconcile, promote, dedupe, prune, extract, snowball, sdb, report |
rag |
Semantic search (optional rag extra; deprecated, moving to a separate tool and removed in 4.0) |
ingest, query, context, purge, model |
Every command has built-in help: zotero-cli <noun> <verb> --help. Every example in these docs is checked against the real command-line parser in CI.
📦 Installation
Supported platforms
| Platform | Standalone binary | Python package (zotero-command-line) |
|---|---|---|
| Linux x86_64, glibc 2.34+ (RHEL 9/AlmaLinux 9, Ubuntu 22.04, Debian 12, Fedora 36 or newer) | ✅ .tar.gz, .deb, .rpm, Docker |
✅ Python 3.11–3.14 |
| Linux x86_64 or ARM64, older glibc (2.17+) | ❌ | ✅ Python 3.11–3.13; 3.14 needs glibc 2.28+ |
| Linux with musl (Alpine) | ❌ | ✅ Python 3.11–3.14 |
| Windows x64 | ✅ .msi, .zip |
✅ Python 3.11–3.14 |
| macOS (Apple Silicon and Intel) | ❌ | ✅ Python 3.11–3.14, via uv tool install or pipx |
CI checks that the Python package installs from pre-built wheels on each of these platforms. Other platforms may work but aren't tested.
The release workflow also runs the built Linux binary (--help, system selftest, and offline commands against a fixture library) inside containers for each of the distros named above, so the glibc floor is checked on every release, not just claimed (Issue #413). The Windows binaries are unsigned: expect a SmartScreen "unknown publisher" warning and possible antivirus false positives on first run; gh attestation verify <file> -R fchicout/zotero-cli (see above) is the way to confirm a download is genuinely from this repository's release workflow without relying on code signing.
Option 1: Standalone binaries (recommended)
Download a pre-built binary for your system. You don't need Python.
- Windows:
.msiinstaller or.zipfrom Latest Releases. - Linux (Ubuntu/Debian):
.debpackage. - Linux (Fedora/RHEL):
.rpmpackage. - Any Linux:
zotero-cli-linux-amd64.tar.gz.
Or use the one-line installer scripts. They fetch the latest release (or the one named in ZOTERO_CLI_VERSION, e.g. v3.0.5) and check it against the release's SHA256SUMS before installing:
# Linux (x86_64, glibc 2.35+; on other systems it prints the PyPI command instead)
curl -fsSL https://raw.githubusercontent.com/fchicout/zotero-cli/main/install.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/fchicout/zotero-cli/main/install.ps1 | iex
Verifying a download yourself: every release from v2.8.12 on includes a SHA256SUMS file and signed build provenance. With the GitHub CLI, gh attestation verify zotero-cli-linux-amd64.tar.gz -R fchicout/zotero-cli confirms the file was built by this repository's release workflow.
Binaries don't bundle the rag extra (local/HF embeddings, torch, sentence-transformers): that stack is large and most installs never touch it. rag commands print a clear error in the binary; install from PyPI with pip install 'zotero-command-line[rag]' (or uv tool install 'zotero-command-line[rag]') for those. Everything else, including offline mode and Markdown/PDF text export, works the same in the binary and from PyPI.
🐍 Option 2: From PyPI (Python 3.11+) - recommended on macOS
The package is called zotero-command-line on PyPI, because the zotero-cli name there belongs to an older, unrelated project. The command it installs is still zotero-cli. This is the way to install on macOS, Alpine, ARM Linux and older Linux systems, where there is no binary. uv can install a suitable Python for you.
uv tool install zotero-command-line # or: pipx install zotero-command-line
zotero-cli --help
🐳 Option 3: Containers
The repo includes a Dockerfile. It builds the same standalone binary as the releases.
git clone https://github.com/fchicout/zotero-cli.git
cd zotero-cli
docker build -t zotero-cli .
# Configure with an env file (ZOTERO_API_KEY=..., ZOTERO_LIBRARY_ID=...,
# ZOTERO_LIBRARY_TYPE=user or group, one per line),
# which keeps keys out of your shell history...
docker run --rm --env-file zotero.env zotero-cli system info
# ...or mount your existing config directory, running as yourself so any
# files zotero-cli writes there (logs, job state) belong to you
docker run --rm --user "$(id -u):$(id -g)" \
-v ~/.config/zotero-cli:/config/zotero-cli zotero-cli system info
The container runs as an unprivileged user and keeps its config in /config/zotero-cli. That directory belongs to the image's own user, so with --user you must mount a directory you own there (as above); otherwise commands that keep state, such as system jobs, stop with an error saying so. On SELinux hosts (Fedora, RHEL), add :z to the mount: -v ~/.config/zotero-cli:/config/zotero-cli:z.
Note:
--offlinemode (reading a localzotero.sqlite) needs that file mounted into the container too, e.g.-v /path/to/zotero.sqlite:/data/zotero.sqlite.
Note: inside a container,
servehas to bind0.0.0.0to be reachable, which needs--allow-remote(it prints an access token). Publish the port on the host's loopback only:docker run -p 127.0.0.1:1969:1969 ... zotero-cli serve --host 0.0.0.0 --allow-remote.
A .devcontainer/ configuration is also included for GitHub Codespaces and VS Code Dev Containers. It sets up the full development environment for contributing, not the lightweight image above.
Option 4: From source (Python 3.11+)
Using uv:
git clone https://github.com/fchicout/zotero-cli.git
cd zotero-cli
uv tool install .
⬆️ Upgrading and uninstalling
Upgrading keeps your configuration (~/.config/zotero-cli, or %APPDATA%\zotero-cli on Windows); uninstalling leaves it too, so delete that directory as well if you want no trace. Read the CHANGELOG before a major upgrade.
| Installed with | Upgrade | Uninstall |
|---|---|---|
install.sh |
run it again (or set ZOTERO_CLI_VERSION=3.0.5 for a given version) |
rm ~/.local/bin/zotero-cli |
install.ps1 |
run it again | delete %LOCALAPPDATA%\zotero-cli\zotero-cli.exe |
.msi |
install the newer .msi; it replaces the old one |
Settings → Apps → zotero-cli → Uninstall |
.deb |
sudo apt install ./zotero-cli_<version>_amd64.deb |
sudo apt remove zotero-cli |
.rpm |
sudo dnf install ./zotero-cli-<version>.x86_64.rpm |
sudo dnf remove zotero-cli |
.tar.gz / .zip |
replace the zotero-cli binary |
delete the binary |
| uv | uv tool upgrade zotero-command-line |
uv tool uninstall zotero-command-line |
| pipx | pipx upgrade zotero-command-line |
pipx uninstall zotero-command-line |
| Docker | git pull and docker build -t zotero-cli . again |
docker rmi zotero-cli |
Installed from git before 2.8.12? That package was named zotero-cli, and pip treats zotero-command-line as a different package, so both can end up installed and fight over the zotero-cli command. Remove the old one first: pip uninstall zotero-cli.
📦 Using zotero-cli as a Python library
pip install zotero-command-line
See the "Distribution" section of docs/ARCHITECTURE.md for details, including installing an unreleased commit from git.
⚙️ Configuration
zotero-cli init # interactive wizard: API key, library, optional metadata providers
zotero-cli system info # show which config is in use
The config file lives at ~/.config/zotero-cli/config.toml (Linux/macOS) or %APPDATA%\zotero-cli\config.toml (Windows). See docs/SETUP_GUIDE.md and config.toml.example.
Development & Contribution
git clone https://github.com/fchicout/zotero-cli.git
cd zotero-cli
uv sync --extra dev
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
uv run pytest tests/unit
uv sync creates .venv/, using the Python version pinned in .python-version, and installs the project in editable mode from uv.lock. Commit uv.lock alongside any dependency change so everyone, CI included, gets exactly the same versions. pre-commit install sets up the checks: ruff (lint and format), mypy and bandit run on every git commit, and pytest tests/unit runs on git push. See .pre-commit-config.yaml.
Read CONTRIBUTING.md before opening a pull request: it lists the checks, the test layout, the rule for commands that delete, and how releases work. Everyone taking part follows the Code of Conduct.
Getting help
Questions about using zotero-cli: open an issue with the Question form. Bugs and feature ideas use their own forms there too. This is a volunteer-run project, so replies can take a few days. Security problems go through SECURITY.md, never a public issue.
Data Sources
Besides your Zotero library, zotero-cli queries public scholarly services (Crossref, Semantic Scholar, OpenAlex, Unpaywall, arXiv, PubMed and others) and identifies itself honestly when it does. docs/DATA_SOURCES.md lists them, links their terms, and explains attribution: if you publish results from slr snowball, credit Semantic Scholar. zotero-cli is an independent project, not affiliated with or endorsed by Zotero, the Corporation for Digital Scholarship, arXiv or any of these providers.
Exit codes
Errors are one Error: … line on stderr, and the exit code says what kind of failure it was (usage, not found, authentication, network, …): see docs/EXIT_CODES.md.
Versioning
zotero-cli follows Semantic Versioning from 3.0.0 on. docs/COMPATIBILITY.md lists what counts as the public interface (commands, flags, safety defaults, JSON/CSV output, config keys, stored formats) and how deprecations work. Upgrading from 2.x: see "Upgrading to 3.0" in the CHANGELOG.
Security
To report a vulnerability, see SECURITY.md. Please don't use public issues for security problems.
License
MIT License. See LICENSE for details.
Metadata
Release files for zotero-command-line 3.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zotero_command_line-3.4.1.tar.gz | 412.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zotero_command_line-3.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 826.0 kB
Release files / zotero_command_line-3.4.1.tar.gz
| Download URL | zotero_command_line-3.4.1.tar.gz |
|---|---|
| Size | 412.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
724e8a8ab7778d9e88d136c6caa0ff8c516fa43a31b39e07680dae8fcbcaa3b0
|
|
BLAKE2b-256 checksum How to use checksums |
b7600a2165aee74b775b6bf18ba1160e5d517a69373b3f84685d4fcbdfbcd7e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zotero_command_line-3.4.1-py3-none-any.whl
| Download URL | zotero_command_line-3.4.1-py3-none-any.whl |
|---|---|
| Size | 414.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f99354dc58976ce1d79f84b7e43bc5c8b350d3e0e18b5d9f3fbcc667e792ffb0
|
|
BLAKE2b-256 checksum How to use checksums |
92fa0dbe647b3f175d03979e436482203152c78c1522327a28fced3265bd67f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|