Skip to main content

Version Build Status Tests License Python

Quality Gate Coverage Bugs Vulnerabilities Code Smells Duplicated Lines Maintainability Reliability Security Security Hotspots Technical Debt Lines of Code

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 list prints json, csv or markdown, and --fields picks the columns. Data goes to stdout and messages to stderr, so you can pipe it into jq, a spreadsheet or a prompt.
  • Suited to LLM agents: an agent can run --help on any command to learn its options, and most commands' help includes worked examples. item export --as md extracts the text of an item's PDF into a .md file an LLM can read.
  • Preview first: collection clean, collection delete --recursive, storage checkout, system restore, item delete, tag purge, item merge, item hydrate and item pdf strip only show what they would do until you add --execute. A recursive delete also asks for confirmation (--yes in scripts), and a collection name that matches several collections is refused rather than guessed. item purge and collection purge ask for confirmation. Deletes of items (item delete, item merge, item transfer --delete-source, collection delete --recursive) move them to Zotero's trash and can be undone with item restore; --permanent deletes for good. --offline reads 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 hydrate to 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 .zaf archive, attachments included.
  • system check tests 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.

🍳 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

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.

Download a pre-built binary for your system. You don't need Python.

  • Windows: .msi installer or .zip from Latest Releases.
  • Linux (Ubuntu/Debian): .deb package.
  • Linux (Fedora/RHEL): .rpm package.
  • 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.

Offline mode and Markdown/PDF text export work the same in the binary and from PyPI. The read-only MCP server (mcp serve) is an optional extra (pip install 'zotero-command-line[mcp]') and is not in the binaries.

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: --offline mode (reading a local zotero.sqlite) needs that file mounted into the container too, e.g. -v /path/to/zotero.sqlite:/data/zotero.sqlite.

Note: inside a container, serve has to bind 0.0.0.0 to 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 4.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zotero-command-line 4.0.2
File Size Uploaded
zotero_command_line-4.0.2.tar.gz 318.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zotero-command-line 4.0.2
File Interpreter ABI Platform
zotero_command_line-4.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 610.6 kB

Release files / zotero_command_line-4.0.2.tar.gz

Download URL zotero_command_line-4.0.2.tar.gz
Size 318.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cef6d1aee5bc611c47617a13a797412a8797de6de4d5fe7da33cb089651af616
BLAKE2b-256 checksum
How to use checksums
5ea084cdbe6b2f0d252179b30549e2891ab3503d70a4ff1291cc4c400df13d29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","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-4.0.2-py3-none-any.whl

Download URL zotero_command_line-4.0.2-py3-none-any.whl
Size 292.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
122438d1722b3128816c6aa69928166a7de92e9850ec97f05e9581341cbb7926
BLAKE2b-256 checksum
How to use checksums
946e2b8bc2768e286465c044d15523a9c6e4ec4f8ace5e04869ef9dab071793b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","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 history Release notifications | RSS feed

This release

4.0.2 This release

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.6.0

2 release files

3.5.0

2 release files

3.4.1

2 release files

3.4.0

2 release files

3.3.1

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.6

2 release files

3.0.5

2 release files

3.0.4

2 release files

3.0.3

2 release files

3.0.2

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.8.12

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page