Skip to main content

citezot

Pull cited references from Zotero into Quarto bibliographies, and find, add and update Zotero items from R.

citezot hex sticker: [@] in white on a red hexagon

License

MIT

Developers

Joel H. Nitta
Author, maintainer

Status

R-CMD-check

The goal of citezot is to make it easy to cite references in Quarto or R Markdown documents using Zotero.

To use citezot, cite sources in your text as usual following Quarto (Pandoc) syntax. For example, manuscript.qmd might contain:

---
bibliography: references.bib
---

Species evolve by natural selection [@Darwin1859]. Extant land plants include two major lineages [@Kenrick1997a].

Both references should be in your Zotero library, with the citekeys Darwin1859 and Kenrick1997a saved for those items.

Calling cz_update_bib() generates the references.bib file containing only the references you cited, straight from Zotero:

library(citezot)
cz_update_bib("manuscript.qmd")
#> ✔ 2/2 cited keys found; 'references.bib' updated.
#> ℹ 2 from 1 document, 0 only in other files.

citezot can also look items up, add them from a DOI, and fix fields, always showing you what it is about to do first.

citezot talks to the Zotero web API, so it works without Zotero desktop running. This means you can use it, e.g., on a server, or to cite references in an automated document generated by GitHub Actions (automated CVs!).

citezot is not affiliated with or endorsed by Zotero.

Installation

citezot is not on CRAN. Install it from R-universe, which provides prebuilt binaries for macOS and Windows:

install.packages(
  "citezot",
  repos = c("https://joelnitta.r-universe.dev", "https://cloud.r-project.org")
)

To install the development version from GitHub instead, use pak:

# install.packages("pak")
pak::pak("joelnitta/citezot")

Setup

You need a Zotero API key and your user ID (kept in ~/.Renviron), and each item you cite needs a citekey. A read-only key is enough for everything except adding and editing items.

See the setup guide for step-by-step instructions.

Usage

Bibliography generation

cz_update_bib("manuscript.qmd") # writes the file named by `bibliography:`
cz_update_bib("manuscript.qmd", out = "references.yaml") # CSL-YAML instead of BibLaTeX
  • The output file defaults to the bibliography: of the document’s YAML front matter (or _quarto.yml), else references.bib. BibLaTeX is the default because it works with every Quarto output format, including Typst.
  • Files that mention sources informally (e.g. a CSV note saying “defined in Darwin1859”) can be scanned too: cz_update_bib(qmd, bare = "data.csv").

It will not quietly lose references

If a key you cited in the document is not in Zotero, cz_update_bib() stops and leaves the existing file untouched, because a bibliography that silently loses entries breaks the render. It will let you know the missing keys and what to do about them. (allow_incomplete = TRUE writes what was found anyway).

Fast enough to run on every render

Fetching a large library takes a while the first time (about 20 s for ~6,000 items, requested in parallel). With caching on, later runs download only what changed: about 0.3 s when nothing did.

# You can put this in your in .Rprofile
# The cache is stored in tools::R_user_dir("citezot", "cache")
options(citezot.cache = TRUE)

Caching is off unless you ask for it. The cache holds bibliographic metadata, never your key; cz_clear_cache() deletes it.

Finding, adding and fixing items

Everything that changes your library is a dry run unless you say otherwise: it reads, shows exactly what would happen, and writes nothing until you add dry_run = FALSE. There is no delete.

cz_find("darwin origin of species") # free text
#>       item           citekey author year
#> 1 MEIQ8FY5        Darwin1859 Darwin 1859
#> 2 MY4FLB35 katz2018extending   Katz 2018
#>                                                                                                                                   title
#> 1                On the origin of species by means of natural selection, or the preservation of favoured races in the struggle for life
#> 2 Extending the scope of Darwin’s ‘abominable mystery’: integrative approaches to understanding angiosperm origins and species richness
#>         doi            type
#> 1      <NA>            book
#> 2 10/gb4dc5 article-journal
cz_find(doi = "10.2307/1796430") # by DOI, ISBN or exact citekey
#>       item       citekey             author year
#> 1 JVIYHN3W MacArthur1967 MacArthur & Wilson 1967
#>                               title             doi type
#> 1 The theory of island biogeography 10.2307/1796430 book
cz_find(citekey = "Darwin1859")
#>       item    citekey author year
#> 1 MEIQ8FY5 Darwin1859 Darwin 1859
#>                                                                                                                    title
#> 1 On the origin of species by means of natural selection, or the preservation of favoured races in the struggle for life
#>    doi type
#> 1 <NA> book

cz_add("10.1016/0022-5193(64)90038-4", key = "auto")
#> ℹ Citekey formula `auth.lower + year + shorttitle(1).lower` read from your
#>   Zotero profile (Better BibTeX).
#> ℹ Dry run: nothing written. Use `dry_run = FALSE` to add this item.
#> • type: journalArticle
#> • title: The genetical evolution of social behaviour. I
#> • authors: Hamilton W.D.
#> • date: 1964-07
#> • in: Journal of Theoretical Biology
#> • DOI: 10.1016/0022-5193(64)90038-4
#> • citekey: hamilton1964genetical
#> This message is displayed once per session.

cz_update_item("Darwin1859", list(publisher = "John Murray"))
#> ℹ Dry run: nothing written. Use `dry_run = FALSE` to apply to item MEIQ8FY5 (On
#>   the origin of species by means of natural sele…).
#> • publisher: "J Murray" -> "John Murray"

Wherever a function takes an item, you can give a DOI, a Zotero item ID, an existing citekey, or a one-row result of cz_find().

  • cz_add() looks the DOI up in Crossref, checks the item is not already in your library (and that the citekey is unused and valid for pandoc), builds the item from Zotero’s own template for its type, and pins the key in the same request. Each request carries a write token, so a retry after a network error cannot create the item twice. Only Crossref DOIs are supported so far.
  • cz_update_item() sends only the fields that actually change, together with the item version. If the item was edited elsewhere in the meantime, Zotero refuses (HTTP 412) and nothing is overwritten. Creators, tags and collections are replaced as a whole, as Zotero does.
  • cz_pin_key() refuses to replace a different pinned key unless you pass overwrite = TRUE, since that would break documents that cite the old one.

Generating citekeys

key = "auto" builds the key from a Better BibTeX-style formula, such as auth.lower + year + shorttitle(1).lower (giving hamilton1964genetical).

The key formula comes from, in order: options(citezot.key_format = "..."); the pattern saved in your Zotero profile by Better BibTeX (citezot reads that one setting from Zotero’s prefs.js, only when it generates a key, and tells you once per session); or Better BibTeX’s default pattern. If the same key already exists, a letter is appended to the new one (a, b, …).

Only a documented subset of the formula language is supported; anything else is an error rather than a different key. Check what is in use with cz_key_format() and try a formula with cz_suggest_key().

item <- list(
  itemType = "journalArticle",
  title = "The genetical evolution of social behaviour. I",
  date = "1964-07",
  creators = list(list(
    creatorType = "author",
    firstName = "W. D.",
    lastName = "Hamilton"
  ))
)
cz_suggest_key(item, "auth.lower + year + shorttitle(1).lower")
#> [1] "hamilton1964genetical"
cz_suggest_key(
  item,
  "auth.lower + year + shorttitle(1).lower",
  existing = "hamilton1964genetical"
)
#> [1] "hamilton1964geneticala"

Automation

Instead of manually running cz_update_bib(), you may want to automatically run it every time your document is rendered.

A Quarto pre-render step in _quarto.yml will do this:

project:
  pre-render: Rscript -e 'citezot::cz_update_bib()'

If you use targets, you could specify a target that is re-checked on every tar_make():

tar_target(
  references,
  cz_update_bib("manuscript.qmd", quiet = TRUE),
  format = "file",
  cue = tar_cue(mode = "always")
)

How it differs from other packages

rbbt is built around RStudio, particularly its add-in for inserting citations while you write. It needs Zotero desktop running with the Better BibTeX plugin.

zot is a general-purpose Zotero client. It works at a lower (more direct) level, with versioned writes, batch plans, file attachments and read-only queries of the local database.

c2z imports references into Zotero from many sources (including DOIs, ISBNs and the Norwegian Cristin registry), and can edit, copy and delete items.

citezot seeks to be lightweight (minimal package dependencies), to make bibliography maintenance simple (one function call builds your bibliography), to be careful with your library (every write is a dry run unless you say otherwise, and nothing can be deleted), and to work across platforms, even without Zotero running.

These descriptions are based on each package’s documentation, not its code. Please check their pages for the current state.

Limitations

  • Item lookup by identifier supports Crossref DOIs only (ISBN, arXiv and PMID are planned).
  • Nothing can be deleted through citezot.
  • BibLaTeX output comes from Zotero’s own exporter, and CSL-YAML from the library’s CSL-JSON, so what you see in Quarto is what Zotero’s exporters produce.
  • Matching is by the citekey stored on the item; items without one cannot be cited.

Development

To work on citezot, clone the repository and install its development dependencies (pak::local_install_dev_deps()). The checks that run before each commit use pre-commit; install it once (for example brew install pre-commit or pip install pre-commit), then enable the hooks in your clone:

pre-commit install
  • Before every commit, the hooks run styler and lintr (tidyverse style, 80 columns; see .lintr) and check that README.md has been rendered from README.Rmd. Run them by hand with pre-commit run --all-files.
  • Before every push, a hook runs devtools::check() and refuses the push unless it has no errors, warnings or notes.
  • The tests make no network calls (devtools::test()). Edit README.Rmd, not README.md, and render it with devtools::build_readme(). Some of its examples read from a Zotero library, so rendering needs Zotero credentials (see the setup guide) and the items cited in the examples; if you cannot render it, say so in your pull request.

Getting help

Documentation, including a worked example, is at https://joelnitta.github.io/citezot/ (or the manuscript guide once installed). Please report bugs and ideas at https://github.com/joelnitta/citezot/issues.

License

citezot is released under the MIT license.