Skip to the content.

Developing Zotero plugins with AI

You use Zotero daily, you are missing a feature, and modern AI coding agents can write most of the code — but a Zotero plugin is a peculiar thing (it runs inside a Firefox-based app, with its own APIs, lifecycle, and community rules), and an agent left alone will guess wrong about all of it. This page serves two purposes:

The shortcut: give this page to your AI. This page is written to be read by AI agents as much as by humans. Paste its URL (or its content) into your AI coding agent together with your plugin idea, and the agent will guide you through the steps below to build the plugin — explaining each one, doing the coding and in-app verification it can do itself, and walking you through the parts that need your hands (installing Zotero, creating the sandbox profile, installing the bridge, testing real gestures). You stay the judgment: read what it writes, test what it ships.

The experience behind this page is with Claude Code in VS Code; the pieces are MCP-standard, so any MCP-capable agent should work.

Part 1: Starting from scratch

What you need

Step 1 — Isolate: a dedicated profile and data directory

Before anything else — and this applies to any Zotero development, with or without an AI agent — separate your development environment from your real library. Create a dedicated Zotero profile with its own data directory, following the official guide: Multiple Zotero profiles (zotero.exe -P opens the profile manager; give the dev profile its own data directory in Settings → Advanced → Files and Folders). Sync it to a throwaway account or not at all.

Everything development does — installing work-in-progress builds, executing scripts, generating test data, letting newer builds upgrade the database schema (a one-way operation) — happens in that profile. An agent that can execute code in Zotero can also corrupt a database; the sandbox makes that a non-event. The agent gets access to the dev profile only, never the one holding your real library.

Isolation, level two — a source-built Zotero. Once you are past the basics, a source-built Zotero (see the official build docs) on a third profile lets you test your plugin against upstream HEAD — weeks before changes reach a beta. It is a different binary, so it runs alongside your daily Zotero; give each instance its own bridge port (Part 2 shows how). Weavero catches upstream collisions (row-model changes, search reworks, docShell behavior changes) this way, on the day they land — and the one-way schema rule above is exactly why this sandbox, too, gets its own data directory.

Two low-friction on-ramps before you build anything. You can learn most of what matters about Zotero’s API without a plugin skeleton:

Step 2 — Start from the template, not from a blank folder

The community maintains a modern plugin stack; starting there saves the agent from reinventing (badly) what already exists:

Step 3 — Frame the agent: ground truth and standing rules

This step matters more than any amount of prompting skill. An AI agent without framing goes in blind: it answers from training data that predates current Zotero, searches the open web where plugin information is sparse, outdated, or plain wrong, and — when it finds nothing — it can hallucinate an API that sounds plausible and does not exist. Framing the agent fixes this at the root, three pieces:

  1. Give it access to the real documentation and tools — locally or through links. Clone the Zotero source (plus reader and note-editor if you touch those areas) somewhere the agent can search, and point it at the documentation list below. A local clone beats a web search every time: the agent greps the exact version you run.
  2. Dictate standing rules it always remembers — instruction files (Claude Code reads CLAUDE.md from the repo root; other agents have equivalents). Record how to build, how to test, the project’s conventions, and every invariant you learn the hard way — this is the context in which the agent takes decisions, and it is reloaded every session, so no lesson has to be learned twice.
  3. Make evidence-before-answers a standing rule. Require the agent to base every claim on real data — read the source code or the documentation (or probe the live app, Step 4) before replying, and state which sources it used. An answer that cites chrome/content/zotero/tabs.js or a documentation page is checkable; an answer from memory is a guess wearing a confident tone. On Weavero this rule is written down as “verify, don’t guess” — Part 2 shows how it is enforced in practice, and the permissions side of the framing completes it.

The agent-side fundamentals have an excellent official tutorial: Best practices for Claude Code covers writing an effective CLAUDE.md (what to include, what to prune), configuring permissions, giving the agent checks it can run, and managing context — the principles transfer to any coding agent. This page stays focused on the Zotero-specific side.

Documentation to point the agent (and yourself) at:

Ask for prior art. The best way to solve a problem is very often to look at what others have done in similar situations — and an AI agent is exceptionally good at that research if you ask for it explicitly:

When code patterns are adapted from what you find, remember the licensing rule below: license compatibly and credit the origin.

Step 4 — Give the agent eyes and hands

The single biggest upgrade to AI-assisted plugin work is letting the agent interact with a live Zotero: execute JavaScript, install and reload builds, read the error console, query the database, take screenshots. That is what the MCP bridge for Zotero provides; the full setup (including running two Zotero instances) is in Part 2. Install the bridge in the dev profile from Step 1 only — the whole point of the sandbox is that the agent’s hands never reach your real library. And capable hands deserve configured limits: see parametrise the agent in Part 2.

Step 5 — Iterate in small, verified steps

The loop that works: describe one small behavior → the agent implements it → typecheck gate → build → install into the sandbox → verify in the running app (the agent can do much of this through the bridge; you test the parts that need real gestures) → commit. Resist big-bang features: agents are excellent at small verified increments and unreliable at thousand-line leaps. Zotero’s install/reload machinery has sharp edges (caching, stale handlers, version rules) — they are catalogued precisely in the edit–install–verify loop and the gotchas index below.

The ecosystem’s rules — read before publishing

Publishing

Release through the scaffold (it builds the XPI, creates the GitHub release, and maintains the update.json that gives your users auto-updates). To be discoverable, get listed in the zotero-addons-scraper registry, which feeds the in-Zotero Add-on Market plugin most users browse. (An official Zotero plugin directory is planned and expected to replace the third-party lists — that official plugins page is the likely place it will appear, so it is the one to watch. Meanwhile the NGI0-funded plugin-ecosystem project — a community effort, not affiliated with Zotero — is consolidating the community tooling above at zotero-plugin.dev, where a community plugin registry is under development.)

Part 2: The Weavero workflow in depth

What makes AI-assisted development work well on Weavero is not only the code generation — it is that the agent acts on what it observed in the running app instead of what it guessed. Everything below was learned building Weavero against Zotero 7–10.

The bridge: let the agent drive a live Zotero

Two components connect the agent to Zotero:

  1. MCP Bridge for Zotero — a small Zotero plugin that opens a Firefox Remote Debugging Protocol (RDP) server inside Zotero (localhost only). Zotero is Firefox under the hood, so the whole DevTools server ships with it; the plugin just opens the socket. Download zotero-mcp-bridge.xpi from the mcp-server-zotero-dev releases and install it via Tools → Plugins → ⚙️ → Install Plugin From File (in the dev profile — see Step 1 of Part 1).
  2. @introfini/mcp-server-zotero-dev — an MCP server (run via npx from the npm package) that translates agent tool calls into RDP requests: zotero_execute_js, zotero_plugin_install, zotero_plugin_reload, zotero_db_query, zotero_screenshot, zotero_read_errors, DOM inspection, prefs, and more. Both halves live in the same repository.

Register it with the agent (one-time):

claude mcp add -s user zotero-dev -- npx -y @introfini/mcp-server-zotero-dev

On Windows, run that in cmd, not PowerShell — PowerShell strips the bare -- separator. And when passing environment variables, the server name must come before the variadic -e flags:

claude mcp add -s user zotero-dev-source -e ZOTERO_RDP_PORT=6101 -- npx -y @introfini/mcp-server-zotero-dev

That second form matters as soon as you run two Zotero instances (say, your installed beta and a built-from-source HEAD): give each its own bridge port and register one MCP server per instance. The port pref the bridge plugin reads is extensions.zotero.extensions.mcp-rdp.port (per profile, not per binary). A brand-new MCP server needs a full agent restart to appear — a mid-session reconnect is not enough.

Teach the project to the agent: instruction files

Three habits carried the most weight in Weavero’s instruction files:

Weavero’s instruction files themselves are private working notes, but their operative content is distilled in this page and in the public testing contract: TESTING.md (which test suite to run on which kind of change), the bench/ performance protocol, and the manual test protocols for what automation can’t reach (restart/session, gestures, plugin-disable, taskbar overlays).

Parametrise the agent: permissions and guardrails

Instruction files teach the agent what is true; the agent’s own configuration decides what it may do — and tuning it is as important as the prompts. Every serious agent has a permission system (in Claude Code: settings.json with allow/deny rules per tool and command pattern). Three settings carry most of the value:

Revisit the lists as habits form: promote commands that always get approved, demote anything that ever surprised you. For the Claude Code specifics (permission modes, allowlists, sandboxing, hooks), see the official best-practices guide; the examples throughout this page use Claude Code’s configuration, but every capable agent has equivalents (permission rules, project instruction files, tool allow/deny lists) — translate the idea, not the exact syntax.

The edit–install–verify loop

  1. Edit TypeScript → npm run build (the scaffold bundles the XPI).
  2. zotero_plugin_install with the built XPI path (bump the version every build — Zotero will not reinstall the same version id).
  3. Force-reload with cache bypass — the bytecode cache happily serves last build’s code after an install. zotero_plugin_reload, or loadSubScriptWithOptions(rootURI + "bootstrap.js", {ignoreCache: true}).
  4. Verify through the bridge: is the new behavior live? any new errors in zotero_read_errors?

Two lifecycle rules the loop depends on:

Scripted in-app testing and benchmarks

The bridge turns tests and benchmarks into plain JavaScript executed inside Zotero: generate fixtures (hundreds of annotations in one DB transaction), drive the UI, sample requestAnimationFrame deltas for frame-time statistics, read back structured JSON. Weavero’s benchmark suite lives in bench/ — reader-load timings, sidebar/PDF scroll jank, window-machinery timings — and it caught real regressions in our own “optimizations” the day they were written. The patterns that matter:

Gotchas index

The compressed list — each of these cost real time once:

Gotcha Rule
PowerShell strips -- register MCP servers from cmd
Bytecode cache after install force-reload with ignoreCache
Same-version reinstall is a no-op bump the version every build
Hot-reload keeps stale DOM handlers re-open tabs / restart before testing
Force-kill loses add-on state always quit gracefully
Xray membrane cloneInto everything passed into reader internals
Untrusted events XUL command handlers ignore synthetic clicks
XHTML prefs panes numeric character references only ( , not  )
data-item-type CSS camelCase (attachmentPDF); kebab-case fails silently
Bridge eval timeout keep scripts < ~20 s, loop from outside
Closures go stale across plugin reloads resolve the live plugin object at event time
localStorage/sessionStorage unreliable in Zotero — use Zotero.Prefs or your own files
Agents write Zotero-7-era code ground them in the per-version developer notes (ESM, standard promises)

What this buys you — honestly

With this setup the agent routinely: diagnoses a startup crash by reading the live error console and bisecting with add-on disable/enable cycles; verifies a fix by driving the exact UI flow; measures a performance claim instead of asserting it; and checks compatibility against an upstream commit the day it lands. What it does not replace: real user gestures (drag-and-drop feel, popup interactions), taste, and the final judgment on what ships. The division of labor that works: the agent proposes and verifies against the live runtime; the human reviews diffs, tests the gestures, and decides.

Resource directory

Category Resource
Interactive console Tools → Developer → Run JavaScript · JavaScript API guide
Script harness / automation Actions & Tags
Template zotero-plugin-template
Build/release tool zotero-plugin-scaffold
Helper APIs zotero-plugin-toolkit
TypeScript types zotero-types
Minimal sample make-it-red
Community handbook doc-for-zotero-plugin-dev · Zotero 8 Plugin Development Guide
Official dev notes dev hub · Zotero 7 · Zotero 8 for developers
Ground truth zotero/zotero · reader · note-editor
AI ↔ live Zotero MCP bridge + server
Agent-side tutorial Best practices for Claude Code (CLAUDE.md, permissions, verification, context)
Community zotero-dev list · forums
Distribution addons-scraper registry · Add-on Market
Community ecosystem project zotero-plugin.dev · NGI0 grant
Worked example Weavero source

This page reflects the Weavero workflow as of July 2026 (Zotero 10 beta). Corrections and additions welcome — open an issue.