Thanks for your interest. This is a solo-maintained plugin and contributions of any size are welcome — from typo fixes to whole features. The notes below should save you a round-trip on the obvious questions.
Open a GitHub Issue using the Bug report template. The template asks for:
- Zotero version (e.g. 7.0.x, 9.0.x)
- docling-serve version (
curl http://localhost:5001/version) - Operating system
- Steps to reproduce
- What you expected vs what happened
- Any relevant log lines from Zotero's Help → Debug Output Logging OR the Browser Toolbox console (filter for
[zotero-docling]or[Docling/*])
Please don't paste large debug logs into the issue body — attach as a file, or paste the relevant ~20 lines around the failure.
Open a Feature request issue. Frame the underlying need rather than a specific implementation if possible — that gives more room to find the best fit.
For broader discussions about Zotero workflows, the Zotero Forums are a better venue than this repo's issues.
Please do not report security issues in public issues. Use GitHub's Private Vulnerability Reporting — details in SECURITY.md.
Requires Node 20 or later (declared in package.json engines and pinned in .nvmrc).
git clone https://github.com/max3925vats/zotero-docling.git
cd zotero-docling
nvm use # if you use nvm — picks up .nvmrc
npm install
cp .env.example .env
# Then edit .env: set ZOTERO_PLUGIN_ZOTERO_BIN_PATH and
# ZOTERO_PLUGIN_PROFILE_PATH. Use a dedicated dev profile.
npm startnpm start launches Zotero with the plugin hot-loaded. Save any source file and the scaffold rebuilds + reloads.
Create a separate Zotero profile for development so you don't risk your real library. Run Zotero with --ProfileManager once to make one, then point ZOTERO_PLUGIN_PROFILE_PATH at its directory.
- macOS path with spaces: don't shell-escape with
\in.env— dotenv reads it literally. Just write the raw path:ZOTERO_PLUGIN_PROFILE_PATH=/Users/you/Library/Application Support/Zotero/Profiles/abc.dev. - HF_TOKEN: set in your shell before running
docling-serveso model downloads aren't rate-limited. - Zotero 9 sandbox: some Web APIs (FormData, Blob, AbortController) aren't bare globals in the plugin sandbox — see
getWebApis()insrc/modules/convert.tsfor the pattern.
Before opening a PR:
npm run lint:check # prettier + eslint (CI enforces this)
npm run build # confirms the production .xpi builds
npx tsc --noEmit # full typecheckOr just npm run lint:fix to auto-format. CI runs the same checks on every PR.
- Branch from
main. Use a short descriptive branch name (e.g.fix-skipifexists,add-export-folder). - One logical change per PR. Smaller diffs get reviewed faster.
- Run the local checks above before pushing.
- PR title and body: see
.github/PULL_REQUEST_TEMPLATE.md— it's pre-filled when you open a PR.
Keep them descriptive and imperative present-tense ("Fix skip on duplicate filename", not "Fixed skipping duplicates"). No rigid prefix convention (no Conventional Commits). Multi-line bodies are welcome for non-trivial changes.
The test/ directory uses Mocha (already wired by zotero-plugin-scaffold) with Chai (BDD expect style) and fast-check for property-based tests.
Run with npm run test.
These follow Yoni Goldberg's JavaScript testing best practices. Keep tests dead-simple and self-explanatory — they're a friendly assistant, not a second production system.
- 3-part test names (Goldberg #1.1): what + under what circumstances + expected result. Example:
"When the title contains double quotes, then they are escaped inside the YAML scalar". Avoid"works"/"handles X". - AAA structure (Goldberg #1.2): each test body has
// Arrange,// Act,// Assertcomment markers, even for one-liners. Costs four characters; saves the reader a parse. - BDD assertions (Goldberg #1.3): use
expect(x).to.equal(y),expect(x).to.include(y), etc. Avoidassert.equaland never write imperative loop-based assertions. - Black-box only (Goldberg #1.4): test exported behaviour, not internals. If you find yourself wanting to test a private function, either export it explicitly or test it through the public path.
- Factories over inline mocks (Goldberg #1.9): test data factories live in
test/_factories.ts. Pass only the fields the test cares about; the factory fills in the rest with realistic defaults. Avoid"Foo"/42placeholder data — use realistic input that mirrors what the function would see in production (Goldberg #1.6). - Property-based tests for invariants (Goldberg #1.7): when a function has a universal invariant (
result.length ≤ max,f(f(x)) === f(x), etc.), back the example cases withfc.assert(fc.property(...))fromfast-check. Shrinking finds the minimum failing input for you. - Expect errors, don't catch them (Goldberg #1.10): use
expect(() => fn()).to.throw(/message/)rather than try/catch. - Two-level categorization (Goldberg #1.12): nest tests under at least two
describeblocks (unit-under-test → scenario or sub-function). One top-leveldescribeper file is enforced byeslint-plugin-mocha. - Tag fast tests (Goldberg #1.11): append
#coldto the top-leveldescribefor tests that do no IO. Lets a future CI step grep for the fast subset.
Manual testing in a dev profile is still expected for UI-affecting changes — describe what you tested in the PR description.
addon/ # static plugin assets (manifest, prefs, FTL locales, XUL)
src/
addon.ts # singleton holding plugin state
hooks.ts # lifecycle hooks (startup, shutdown, prefs events)
index.ts # entry point
modules/
convert.ts # core: read PDF → POST to docling-serve → attach .md
menu.ts # right-click menus + batch orchestration
notifier.ts # auto-convert on PDF import (debounced queue)
preferenceScript.ts # prefs pane dynamic behaviour
ui.ts # ProgressWindow wrappers (incl. blur-aware managed)
utils/ # small helpers (concurrency, db lock, frontmatter, …)
typings/ # scaffold-generated d.ts files (committed)
By contributing, you agree your contributions are licensed under AGPL-3.0-or-later — the same as the rest of the project.
This project follows the Contributor Covenant. Treat fellow contributors and users with respect.
This plugin stands on:
- Docling — the conversion pipeline
- docling-serve — the HTTP wrapper we talk to
- zotero-plugin-template and zotero-plugin-toolkit by windingwind — the scaffold and helpers
- The Zotero team — for an extensible, scriptable reference manager