Add a second CI job that runs after the versioned release is published. It upserts a permanent Gitea release tagged 'release', replacing its assets with the freshly built update.json and update-beta.json. This is the URL Zotero polls for auto-update checks per manifest.json.
Zotero Org Export Annotations
Export PDF and EPUB annotations from Zotero to Org-mode files for use with Emacs, org-roam, and citar.
Features
- Export annotations to individual Org files named by citation key
- Automatic export after Zotero sync completes
- Export when closing a PDF/EPUB reader tab
- Configurable output directory
- Optional attachment of Org files to Zotero items
Requirements
- Zotero 7.0+
- Better Notes plugin — must be installed before this plugin
- Pandoc — for Markdown to Org conversion
Installation
- Install the Better Notes plugin first (see its own instructions)
- Install Pandoc and ensure it is on your
PATH(or note its full path) - Download the latest
.xpifile from Releases - In Zotero, go to Tools → Add-ons
- Click the gear icon (top-right) and select Install Add-on From File...
- Select the downloaded
.xpifile and restart Zotero if prompted
Configuration
After installation, open Tools → Org Export Preferences to configure:
| Preference | Default | Description |
|---|---|---|
| Notes Directory | (empty) | Directory where Org files will be saved. Supports ~ for home. |
| Pandoc Path | pandoc |
Full path to the pandoc binary if not on PATH |
| Attach Org file to item | false |
Link exported Org files as Zotero attachments |
| Auto-export on sync | true |
Export automatically after Zotero sync completes |
| Export on tab close | true |
Export when closing a PDF/EPUB reader tab |
| Show notifications | true |
Display a progress notification during export |
| Debug mode | false |
Enable verbose logging to the Zotero error console |
Usage
Manual Export
- Export all items: Tools → Export All Annotations to Org
- Export selected items: Right-click one or more items → Export Annotations to Org
Automatic Export
When enabled in preferences, exports happen automatically:
- After a Zotero sync completes (only items with new/changed annotations are exported)
- When you close a PDF or EPUB reader tab
Output Format
Files are written to the configured Notes Directory, named by citation key:
~/org/notes/smith2020.org
~/org/notes/doe2019.org
Each file contains:
#+title: The Full Title of the Referenced Work
* Annotation heading
Converted annotation text...
Only items that have a citation key (via Better BibTeX or the citationKey field) will be exported.
For Maintainers
This section covers building, developing, and releasing the plugin.
Architecture
src/
├── index.ts # Entry point: mounts addon to Zotero global
├── addon.ts # Core addon class (logging, init, module wiring)
├── hooks.ts # Lifecycle hooks (startup/shutdown, menu actions)
└── modules/
├── prefs.ts # Typed read/write for all preferences
├── exporter.ts # Attachment detection, sync checking, batch export
├── converter.ts # Markdown → Org via pandoc, file I/O
├── notifier.ts # Zotero event handlers (sync finish, tab close)
└── menu.ts # Menu registration (Zotero 7 + 8 compatible)
addon/ # Static plugin files copied verbatim into the XPI
├── manifest.json
├── bootstrap.js # Zotero plugin lifecycle entry point
├── prefs.js # Default preference values
└── content/
├── preferences.xhtml # Preferences pane UI (XUL)
├── scripts/preferences.js
├── icons/
└── locale/en-US/ # Fluent (.ftl) string bundles
The build system uses zotero-plugin-scaffold with esbuild, which bundles src/ into a single index.js and packages everything as an XPI.
Prerequisites
With Nix (recommended):
# Requires Nix with flakes enabled
nix develop
This provides Node.js 20, npm, TypeScript, ESLint, Prettier, and Pandoc.
Without Nix:
- Node.js 20+
- npm
- Pandoc (for manual testing)
npm install
Environment Setup
Copy the environment template and fill in your local paths:
cp .env.example .env
Edit .env:
# Path to the Zotero binary (required for the dev server)
ZOTERO_PLUGIN_ZOTERO_BIN_PATH=/path/to/zotero
# Path to a Zotero development profile directory
ZOTERO_PLUGIN_PROFILE_PATH=/path/to/zotero-dev-profile
Create a dedicated Zotero profile for development to avoid affecting your main library.
Development Workflow
# Start the dev server — builds and hot-reloads the plugin into a running Zotero instance
npm start
# One-off build — outputs XPI to .scaffold/build/
npm run build
# Lint TypeScript source
npm run lint
npm run lint:fix # auto-fix
# Format source files
npm run format
npm run format:check # check without writing
The dev server (npm start) watches for changes in src/ and addon/, rebuilds, and reloads the plugin inside Zotero automatically. Zotero must already be running with the configured profile.
Testing
There are no automated tests. To test manually:
npm run buildto produce the XPI- Install the XPI in a Zotero 7 instance via Tools → Add-ons → Install Add-on From File...
- Exercise the export functionality manually via the Tools menu and right-click context menu
Releasing
Releases are created by pushing a version tag. The CI/CD pipeline (Gitea Actions) will build the XPI and publish a release automatically.
To cut a release locally:
npm run release
This runs bumpp interactively to select the new version, then:
- Updates
package.jsonversion - Commits and tags the version
- Pushes the commit and tag to the remote
- Builds the XPI
Once the tag is pushed, Gitea Actions takes over and creates the release with the XPI attached.
Code Style
- Formatter: Prettier (config in
.prettierrc) — double quotes, 2-space indent, semicolons - Linter: ESLint with TypeScript-ESLint (config in
.eslintrc.js) — strict mode - Naming: PascalCase for classes/types, camelCase for functions/methods, UPPER_SNAKE_CASE for constants
- Imports: relative paths,
import typefor type-only imports, no namespace imports - Error handling: always catch with a typed
error as Error; use_errorfor non-critical ignores
See AGENTS.md for the full style guide used during AI-assisted development.
License
GPL-3.0-or-later
Credits
Based on the zotero-plugin-template by windingwind.