Two small, dependency-free plugins for Zotero 10, written for a real literature-review workflow: one checks that reference metadata is actually correct, the other gets the attached PDFs back out of Zotero.
| Plugin | What it adds | Docs |
|---|---|---|
ref-verifier |
Right-click → Verify Metadata (Crossref) — checks every field of the selected references against Crossref and OpenAlex and applies only the corrections you approve | README |
pdf-exporter |
Right-click → Export PDFs to Folder… — copies the PDFs of the selected items into a folder you pick, keeping their filenames | README |
Both are plain JavaScript. No build step, no bundler, no npm dependencies —
manifest.json + bootstrap.js + two SVG icons, zipped into an .xpi.
A reference list is only as good as the metadata behind it. Volume and issue numbers get mangled by import translators, page ranges go missing, DOIs point at the wrong record, an author gets dropped, a title arrives with raw publisher HTML inside it. None of this is visible while you work — it only shows up in the formatted bibliography, usually after submission.
Zotero can fetch metadata, but it has no way to re-check a record you already have. The nearest thing is deleting the item and re-importing it by DOI, which throws away your tags, notes, collections and attachments.
ref-verifier fills that gap: it compares what you have against authoritative
sources field by field and shows you a diff, so you decide what changes.
Running it over a 95-item library found two genuine defects that would have reached the bibliography:
- a conference paper missing one of its four authors
- a journal article whose stored title contained literal markup —
Assessment of ... the <span style="font-variant:small-caps;">S</span>ustainable ...
Zotero stores attachments in a content-addressed folder tree — every PDF sits alone in a directory named after an internal key. There is no supported way to say "give me the PDFs for these 40 references, in one folder."
Zotero's own source actually contains a function for this,
ZoteroPane.exportSelectedFiles() (zoteroPane.js:6567, commented
"TEMP: Quick implementation"), but the menu item bound to it is
hidden="true" — it was never exposed. It also lives in the File menu rather
than the item context menu.
pdf-exporter exposes that capability properly, from the place you'd look for
it, and keeps the filenames your reference manager gave the files.
Both plugins use the same Zotero 10 bootstrapped-plugin skeleton:
manifest.json metadata + compatibility range
bootstrap.js startup() / shutdown() lifecycle hooks
chrome/content/*.svg icons
startup() registers a chrome:// package (so the plugin's own menu icon can be
resolved) and adds a menu entry through Zotero.MenuManager.registerMenu(),
targeting main/library/item — the item context menu. shutdown() unregisters
both, so the plugins load and unload cleanly without restarting Zotero.
Everything else is ordinary async JavaScript against Zotero's item API.
Zotero 10 is recent enough that writing against remembered APIs does not work.
Every API used here was verified against the running application's own source,
extracted from Zotero.app/Contents/Resources/app/omni.ja, before being used.
That process caught several things that guesswork would have gotten wrong:
- Zotero requires
applications.zotero.update_urlin the manifest. Without it, installation fails with a misleading "may be incompatible with this version of Zotero" dialog (Extension.sys.mjs:1875). Zotero.MenuManagerrenders menu labels only through Fluent l10n IDs. A menu registered without a resolvable FTL bundle appears as a blank row. Both plugins set the label directly on the element instead, which cannot fail.- Menu icons must be filled paths using
fill="context-fill"to pick up the theme colour and invert on the selected row. Stroked icons do not work. - Zotero 10's schema (version 44) added
publisher,place,sectionandpartNumberto thejournalArticletype — fields that did not exist in earlier versions and that change what a metadata checker should report.
Download the .xpi from
Releases, then in
Zotero: Tools → Plugins → gear icon → Install Plugin From File…
The .xpi files are not committed to the tree — they are build artefacts, and
keeping them in git invites the packaged version drifting out of step with the
source. Each release is built from the source at its tag.
To build one yourself, from inside the plugin's folder:
zip -qr ../my-plugin.xpi manifest.json bootstrap.js chromeBump version in manifest.json when reinstalling over an existing copy.
Both manifests point update_url at update.json in this
repository, in the format Zotero's AddonUpdateChecker expects. Zotero checks
it once a day (extensions.update.interval, with extensions.update.enabled
and autoUpdateDefault both on by default), so an installed copy picks up new
releases on its own.
Cut a release with the script rather than by hand:
./release.sh ref-verifierIt reads the version from that plugin's manifest.json, refuses to run if the
plugin has uncommitted changes, builds the .xpi, creates or replaces the
matching release, and regenerates update.json from the manifests before
committing it.
update.json is generated, never edited. Editing it by hand is how this breaks:
bump version but leave update_link pointing at the previous tag, and Zotero
announces an update and then installs the old build — silently, with no error
anywhere. Generating the file makes that state unrepresentable.
Compatibility is declared as Zotero 8.999–10.*. Zotero 11 will refuse to
install these until the range is widened, which is deliberate: the plugin APIs
they use were verified against Zotero 10 specifically, and Zotero 10 changed
enough from 7 that claiming forward compatibility would be a guess.
There is no Zotero test harness, so the tests load the real bootstrap.js,
stub the parts of the Zotero API each plugin touches, and exercise the logic
directly. They run on plain Node with no dependencies:
cd ref-verifier/tests && node suite.mjs && node types-suite.mjs # 143 tests
cd pdf-exporter && node tests/suite.mjs # 21 testsref-verifier's harness reads Zotero's field schema out of the installed
application rather than a checked-in copy, so the tests track whatever Zotero
version is on the machine. That is how the schema-44 field additions above were
noticed.
ref-verifier sends the DOI, title, author surname and year of the items you
select to Crossref (api.crossref.org) and OpenAlex
(api.openalex.org) in order to look them up. Nothing else leaves the machine:
no library dump, no account identifier, no telemetry, and nothing at all until
you run the action on a selection. No API keys are used or required.
pdf-exporter makes no network requests whatsoever.
MIT. Use them, fork them, break them.