# Zotero Annotations
An Obsidian plugin that automatically shows Zotero PDF annotations in a sidebar when your cursor is on a `zotero://select/library/items/ITEMKEY` link.

*With the cursor on a Zotero link, the sidebar shows the paper: its abstract, the notes that mention it, and its Zotero notes and annotations.*

*With a literature note open, the sidebar shows that paper's annotations, grouped by page. The note itself holds the paper's Zotero note, where quotes and citations link back to the PDF.*

*The **Papers** button lists the papers a note cites, along its headings. Click one to see its annotations.*
*On a phone or tablet, tapping a Zotero link opens the sidebar too, from the copy of the annotations kept in the paper's literature note.*
## Features
- **Automatic sidebar**: Place your cursor on a Zotero link (or inside a markdown link `[text](zotero://...)`) and the sidebar opens with that paper's annotations
- **Click interception**: Clicking a `zotero://select/` link opens the sidebar instead of switching to Zotero (double-click to open it in Zotero)
- **Papers in a note**: the **Papers** button lists the papers cited in the current note, grouped under its headings
- **Paper metadata**: Title, authors, date, and collapsible abstract
- **Notes**: Zotero notes are displayed (expanded by default) with embedded images
- **Annotations**: Highlights, comments, and tags grouped by page, with colored sidebar matching the annotation color
- **Image annotations**: Area highlights are rendered from the Zotero cache
- **Pin/freeze**: Pin the sidebar to keep the current annotations while you navigate
- **PDF links**: Click a page number to open the PDF at that page in Zotero
- **Caching**: Annotations are cached in memory to avoid repeated API calls
- **Literature notes** (optional): one note per paper holding its Zotero notes, kept in sync with Zotero in both directions — see below
- **Search Zotero** (Cmd/Ctrl+Alt+Z): find an item of your library and insert a link to it — see below
- **Drag and drop**: drag an annotation from the sidebar into a note to quote it with a link back to the PDF
- **Dead links repaired**: a link to an item merged into another (or deleted) can be pointed at the item that replaced it — see below
## Requirements
- Obsidian desktop to talk to Zotero. On mobile (or when Zotero is not running), the sidebar shows the copy of the annotations kept in literature notes — see below
- **Zotero 7+** running locally (Zotero 10+ to send literature note edits back to Zotero)
- Zotero's local API enabled: **Settings → Advanced → "Allow other applications on this computer to communicate with Zotero"**
## Getting Zotero links
The plugin reacts to two kinds of links:
- a paper: `zotero://select/library/items/ITEMKEY`
- an annotation: `zotero://open-pdf/library/items/ATTACHMENTKEY?page=3&annotation=ANNOTATIONKEY`
Zotero has no built-in command to copy them, but the [Actions & Tags](https://github.com/windingwind/zotero-actions-tags) add-on can, with a script. [**Actions & Tags examples**](docs/actions-tags.md) has ready-made actions — copy a Zotero link, a Markdown link, or an annotation as a quote — to import in one go ([`actions-tags.yml`](docs/actions-tags.yml)) or create one by one:
1. Install Actions & Tags: download the `.xpi` from its [latest release](https://github.com/windingwind/zotero-actions-tags/releases/latest), then in Zotero **Tools → Add-ons → ⚙ → Install Add-on From File…**
2. In **Zotero Settings → Actions & Tags**, import [`actions-tags.yml`](docs/actions-tags.yml), or click **+** and create an action with **Event** None, **Operation** Script, and as **Data** the [Copy Zotero links script](docs/actions-tags.md#copying-zotero-links)
3. Give it a **Shortcut**, select a paper in the library — or an annotation in the PDF reader's sidebar — run the action (shortcut or right-click menu), and paste into Obsidian.
The plugin's settings link to the same page, under **Getting Zotero links**.
## Searching Zotero

**Search Zotero and insert a link** (Cmd/Ctrl+Alt+Z, which you can change under **Settings → Hotkeys**) opens a search of your library. It starts from the selected text, if any. Words match titles, authors and years, as in Zotero's quick search. Fields narrow the search:
| Field | Example | Matches |
|---|---|---|
| `author:` (`au:`, `creator:`) | `author:"van der Berg"` | a creator's name contains it |
| `title:` (`ti:`) | `title:"all you need"` | the title contains it |
| `year:` (`y:`) | `year:2017`, `year:2015-2019`, `year:2020-` | the year, or a range |
| `tag:` | `tag:"to read"` | the item has this tag (exact name) |
| `type:` | `type:book`, `type:journalArticle` | the item type |
A `-` before a field excludes what it matches (`-tag:read`). Accents and case are ignored.
Results appear as you type, each offering the labels its link can have: click one to insert a link to the item, `[label](zotero://select/library/items/KEY)`, at the cursor (replacing the selection). Enter in the search box inserts the first result with the first label. The buttons on the right show the item in the sidebar or select it in Zotero.
Labels are set under **Search link labels**, one template per line, in the order shown. The defaults are:
```
{title} ({citation})
({citation})
{title}
```
A template can use `{title}`, `{authors}` ("Doe et al."), `{year}`, `{citation}` ("Doe et al., 2020"), `{key}`, `{citekey}` (Zotero's citation key, when the item has one) and `{type}`. `({citation})` gives the citation links that literature notes use, so they go back to Zotero as citations.
## Literature notes
Turn on **Settings → Zotero Annotations → Literature notes** to keep one note per paper in the vault. A literature note is identified by its `zotero-item` frontmatter property and looks like this:
```markdown
---
zotero-item: ABCD1234
title: …
citekey: …
zotero: zotero://select/library/items/ABCD1234
zotero-pdf: zotero://open-pdf/library/items/EFGH5678
pdf: file:///…/paper.pdf
zotero-note: NOTEKEY
authors:
- Jane Doe
- John Smith
keywords:
- dense retrieval
---
The Zotero child note, as Markdown ← refreshed from Zotero while you haven't edited it
```
When the paper has several Zotero notes, each one gets its own section instead (and `zotero-note` goes away):
```markdown
%%zt-note: NOTEKEY%%
A Zotero child note, as Markdown
%%/zt-note%%
```
- **Creating notes**: on demand (the **Note** button in the sidebar, or the "Open literature note…" command), when the cursor lands on a link to the paper, or for every paper linked from the vault — see the **Create notes** setting. The automatic modes only create notes for papers that have Zotero notes; an empty note is only made on demand.
- **Sync**: the plugin asks Zotero what changed since the last check (every minute by default, when Obsidian regains focus, and when a literature note is opened) and updates only the affected notes. It never reads Zotero's database directly — everything goes through the local API.
- **Annotations** are not copied into the note: the sidebar shows them, and follows the literature note you have open.
- **Names**: new notes are named "Short title – KEY" (e.g. "Don t Forget Your Embeddings – CEW9LK4V"); the title is cut at the first ":" and to 6 words (both configurable).
- **Refresh**: the sidebar's refresh button, the note's file menu ("Refresh from Zotero") or the "Refresh literature note from Zotero" command re-reads the paper from Zotero.
- **Authors**: the `authors` property lists the paper's authors ("First Last"), refreshed from Zotero. Edit the list in Obsidian (add, remove, reorder; "Last, First" for a new name with a last name of several words) and the plugin asks before sending it to Zotero: once you stop typing for 30 s, when you leave the note, or with "Send literature note edits to Zotero". Editors and other creators are not listed, and kept as they are.
- **Keywords**: the `keywords` property lists the paper's Zotero tags, synced both ways without asking: tags added or removed in Obsidian are added or removed in Zotero, and the other way round (changes made on both sides are merged).
- **Properties**: title, citation key and links are properties, refreshed from Zotero: `zotero` selects the paper in Zotero (in its own literature note, clicking it opens Zotero rather than the sidebar), `zotero-pdf` opens the PDF in Zotero's reader, `pdf` opens the file itself. The body holds only your text and the Zotero notes.
- **Your edits are safe**: text outside the `zt-note` sections is never touched (a note holding a single Zotero note has no such text: the whole body is the note). A note or `zt-note` section you edited in Obsidian is not overwritten: it is sent to Zotero (when you leave the note, after 30 s without typing, or with "Send literature note edits to Zotero"; the first time, Zotero asks for permission — choose "Always allow"). If the note changed in Zotero too, you choose which version to keep. A new `%%zt-note%%` section (without key), or the text of a note for a paper without Zotero notes, becomes a new Zotero note. Images go along (PNG and JPEG; Zotero imports them when the note is opened in Zotero). Deleting a `zt-note` section keeps it out of the note. Holding exactly one Zotero note and nothing else, a note drops its markers. See [When Obsidian and Zotero differ](#when-obsidian-and-zotero-differ) for every case.
- **Links between papers**: in a Zotero note section, a wikilink to another literature note — `[[RePo Language Models with Context Re-Positioning – U8NBCFLN|RePo: Language Models with Context Re-Positioning]]` — becomes a citation of its paper in Zotero, with the link's text (or the note name). The other way round, a Zotero citation of a paper that has a literature note (without a page number) becomes a wikilink to that note, with the citation's text: `[[… – U8NBCFLN|(Li et al., 2025)]]`. Other citations stay `zotero://select` links.
- **Images** inside Zotero notes are copied to the image folder.
- **Text not written by the plugin** (a migrated ZotLit note, for instance) goes to Zotero only when you say so: "Send literature note edits to Zotero" asks whether it becomes a Zotero note (or stays in Obsidian only).
- **Offline copy**: a literature note ends with a `zotero-annotations` block holding what the sidebar shows (paper details and annotations, as JSON; image annotations are copied to the image folder). It is shown as a single line ("12 Zotero annotations, copy of …") and cannot be edited in Obsidian. When Zotero cannot be reached — on your phone, or with Zotero closed — the sidebar shows this copy, marked "Offline copy". It is updated with the note, when the annotations change in Zotero.
### When Obsidian and Zotero differ
A *section* is a `%%zt-note: KEY%%` section, or the whole body of a note holding a single Zotero note (its key in `zotero-note`). "Sent" means sent at the next push: 30 s after you stop typing, when you leave the note, before each sync, or with "Send literature note edits to Zotero". A question is asked once per session when it comes from an automatic push or sync, and each time with "Send…".
| In Obsidian | In Zotero | What the plugin does |
|---|---|---|
| Section unchanged since the last sync | Note changed | Section refreshed from Zotero |
| Section edited | Note unchanged | Sent: it replaces the Zotero note |
| Section edited | Note changed | Nothing is overwritten. "Send…" asks which version to keep; otherwise a notice, once |
| Section unchanged | Note moved to the trash | Section removed at the next sync (a single-note body is emptied, `zotero-note` removed) |
| Section edited | Note moved to the trash | After the sync, asks: remove it from Obsidian, restore the note from the Zotero trash (then the edit is sent), or decide later. For a note deleted for good, the second choice sends the text as a new Zotero note |
| Section emptied | Note unchanged | At the next push, asks: move the Zotero note to the Zotero trash (the section, or `zotero-note`, goes away), bring its text back from Zotero, or decide later |
| Section emptied | Note changed | Same question, warning that Zotero's changes would go to the trash too |
| Section emptied | Note moved to the trash | Section (or `zotero-note`) removed at the next sync |
| Section deleted (markers too) | Note still there | Not brought back (shift-click on the sidebar's refresh to get it back); the Zotero note stays |
| — | New note | Added as a section at the end (a single-note body gets markers) |
| New `%%zt-note%%` section, without key | — | Sent as a new Zotero note; the key is written in the marker |
| Text with no section and no `zotero-note` (a note created when the paper had no Zotero note) | No note | Sent as a new Zotero note; `zotero-note` is set |
| Same | Notes not in the Obsidian note | Not sent, and Zotero's notes are not added, to avoid a duplicate. The sync and "Send…" ask: link the text to one of Zotero's notes (the Obsidian text replaces it), create a new Zotero note, keep it in Obsidian only (Zotero's notes are then added after it), or decide later. Background sends show a notice, once |
| Text outside the sections of a note with several | — | Stays in Obsidian only |
| Text not written by the plugin (migrated ZotLit note), no section | No note | Sent only if "Send…" is confirmed (or kept in Obsidian only) |
A note deleted for good in Zotero (emptied from the trash) is not reported by Zotero's local API: its section goes away only when the paper is refreshed ("Refresh from Zotero", "Refresh all").
### Coming from ZotLit
[ZotLit](https://github.com/PKM-er/obsidian-zotlit) identifies its literature notes by `zotero-key`; this plugin uses `zotero-item`, so the two never manage the same note (before version 0.7, this plugin used `zotero-key` too: its own notes — named "… – KEY", with the offline copy, without ZotLit's `%%zt-managed%%` section — are moved to `zotero-item` on the first start, the others are left alone).
A ZotLit note is only read: when it is open, the sidebar shows its paper, with two buttons:
- **Migrate…** makes it a note of this plugin: `zotero-key` becomes `zotero-item`, ZotLit's generated section (between `%%zt-managed%%` markers) is kept as plain text or removed (the sidebar shows the annotations), the note can be moved to the notes folder and renamed "short title – KEY", then it is refreshed from Zotero (properties, Zotero notes, offline copy). Its text is not sent to Zotero unless you say so. "Migrate ZotLit literature notes…" does it for every ZotLit note of the vault, "Migrate this ZotLit literature note…" for the open one. Notes of papers that have a note of this plugin already, of group libraries, and the Zotero notes ZotLit imported as notes of their own (`zotero-note-key`) are left as they are.
- **Create own note** creates a separate note of this plugin, in its folder; the ZotLit note stays ZotLit's.
### Merged or deleted items
When Zotero merges duplicates, it keeps one item and moves the others to the trash, so links to them stop working. For such a link, the sidebar shows **Find the current item** (also for an item in the trash); the "Repair links to deleted or merged Zotero items" commands check every paper link of the current note, or of the whole vault — malformed keys included (a key that is not 8 characters). Items merged in Zotero (Zotero records which item was kept) are relinked right away: every link to the old item in the vault points at the item kept, and its literature note moves to it (`zotero-item`, name) and is refreshed from it — unless the item kept has a literature note already, in which case the report says so.

A report lists what was done, followed by the items Zotero has no record for. For each one it shows what is known of it (from Zotero's trash, else from its literature note: title, authors, year; else from the text of its links), its literature note, the notes linking to it, and a search of your library. Select the queries to combine — title, first author and year, the titles and citations of its links, e.g. "Suslik 2026" — or type one; the item most like it is marked "best match". You can also search the web for it (Google Scholar, Semantic Scholar, Google). Pick the item with **Use this item**; nothing changes before. Items are searched when the report opens, unless you turn **Search automatically** off — then **Search all** searches them all at once. Clicking a note in the report opens it, with a notice to go back to the report (or use "Show the last report of Zotero link repairs"); it comes back as you left it, searches included. Its text can be selected and copied.
### On mobile
Zotero only runs on a computer, so on a phone or tablet the plugin works from the vault: the sidebar shows the offline copy of the paper's literature note (only papers with a literature note), the "Mentioned in" section and the Papers list work as on desktop, and tapping a `zotero://select` link shows the paper in the sidebar, as on desktop (its **Note** button opens the literature note). Nothing is sent to Zotero from mobile; edits made there are sent by the computer once the notes are synced to it.
## Network use and files outside the vault
- **Network**: the plugin only talks to Zotero running on your own computer, through its local API (`http://localhost:23119`). Nothing is sent to any remote server, and there is no telemetry.
- **GitHub**: when a note cannot be converted safely for Zotero, the plugin offers to open a pre-filled GitHub issue in your browser. That issue contains the note's text; it is only submitted if you click "Submit" on GitHub yourself.
- **Files outside the vault** (read-only, through Node's `fs`): Zotero's local API serves neither the images of area annotations nor the files of images embedded in notes, so the plugin reads them in Zotero's data directory (set in the plugin settings, `~/Zotero` by default): annotation images in `cache/library/`, note images in `storage/` (at the path the API gives). They are shown in the sidebar, and copied into the vault when you drag an annotation into a note, or for literature notes (images in their Zotero notes, and every image annotation for the offline copy). The plugin never writes outside the vault.
- **Vault notes**: to list the notes that mention a paper ("Mentioned in"), the plugin reads the Markdown notes of the vault, looking for `zotero://` links. The index is kept in the plugin's folder (`mention-index.json`) and never leaves your computer.
## Installation
### From Obsidian
**Settings → Community plugins → Browse**, search for "Zotero Annotations", install and enable it.
### Manual
1. Download `main.js`, `manifest.json` and `styles.css` from the [latest release](https://github.com/bpiwowar/obsidian-zotero-annotations/releases/latest)
2. Copy them to `/.obsidian/plugins/zotero-annotations/`
3. Enable the plugin in Obsidian: **Settings → Community plugins → Zotero Annotations**
### Development
1. Clone the repository
2. `npm install`
3. Create a `.obsidian-plugin-dir` file containing the path to your vault's plugin directory:
```
/path/to/vault/.obsidian/plugins/zotero-annotations
```
4. `npm run watch` — rebuilds on every file change
5. Install the [Hot Reload](https://github.com/pjeby/hot-reload) plugin and run `touch .hotreload` in the vault's plugin directory for automatic reloading
You can also set the `OBSIDIAN_PLUGIN_DIR` environment variable instead of using the `.obsidian-plugin-dir` file.
## Commands
| Command | Description |
|---|---|
| Toggle Zotero Annotations sidebar | Show/hide the sidebar |
| Search Zotero and insert a link | Searches the library and inserts the item picked (Cmd/Ctrl+Alt+Z) |
| Pin/Unpin annotations sidebar | Freeze the sidebar so cursor movements don't update it |
| Refresh current annotations | Clear the cache and re-fetch annotations for the current item |
| Open literature note of the paper in the sidebar | Opens the paper's literature note, creating it if needed |
| Refresh literature note from Zotero | Re-reads the active literature note's paper from Zotero |
| Send literature note edits to Zotero | Sends the Zotero notes of the active literature note to Zotero |
| Sync changed literature notes with Zotero | Updates every literature note whose paper changed in Zotero |
| Refresh all literature notes from Zotero | Re-reads every literature note from Zotero, changed or not (sections edited in Obsidian are kept); also adds the offline copy to older notes |
| Send all literature note edits to Zotero | Sends the edited sections of every literature note to Zotero, asking when a note changed on both sides |
| Sync all literature notes with Zotero | Sends all edits, then refreshes all literature notes |
| Migrate ZotLit literature notes… | Makes the ZotLit notes of the vault notes of this plugin (see [Coming from ZotLit](#coming-from-zotlit)) |
| Migrate this ZotLit literature note… | The same for the open note |
| Repair links to deleted or merged Zotero items in current note | Points the note's links to items no longer in Zotero (merged, deleted) at the items that replaced them, in the whole vault |
| Repair links to deleted or merged Zotero items in all notes | The same for every Zotero link of the vault |
| Show the last report of Zotero link repairs | Reopens the last repair report as you left it |
## Known limitations
- Only personal libraries are supported (`users/0`). Group libraries use a different API path.
- Image annotations require access to the Zotero data directory (`~/Zotero/cache/library/`). If your Zotero data directory is in a non-default location, images won't load.
## License
MIT