Library / SDK
ChenglongMa/zoplicate avatar
ChenglongMa/zoplicate

Zoplicate: duplicate detection for Zotero that merges instead of deleting

A plugin that does one thing only: Detect and manage duplicate items in Zotero.

1,015 stars19 forksTypeScriptAGPL-3.0

At a glance

What is it?
Zoplicate is a Zotero plugin in TypeScript that detects duplicate items on import, lets you choose which copy becomes the master, and merges the useful fields into the survivor rather than dropping data. It targets Zotero 9 and 10, and its cross-device non-duplicate list was switched off again in version 5.0.1 after it broke Zotero sync.
Who is it for?
Adopt Zoplicate if your Zotero library accumulates duplicates from imports, web savers or PDF tools, and you want a merge that keeps the fields you care about instead of a delete. Do not rely on it for cross-device sharing of non-duplicate decisions, because version 5.0.1 disabled that sync path after it produced the Invalid setting error in Zotero, and those pairs now live only on the machine where they were made.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One job: find the copies Zotero already knows about

Zoplicate is described as a plugin that does one thing only, detect and manage duplicate items in Zotero. The problem it addresses is specific rather than general. Zotero already has a duplicate detection mechanism, and its built-in Duplicate Items pane lists what it finds, but that pane leaves the user to resolve each pair by hand. Zoplicate adds a workflow around that: when you import items, the plugin checks whether the newly imported ones duplicate what is already in the library, and it offers to handle them for you. The scope is deliberately narrow. It is not a metadata enrichment tool, not a citation manager, and not a replacement for Zotero's own detection, which the project says its improvements are based on. That narrowness is a virtue for a plugin, because the risk of a reference manager is that it quietly rewrites your library, and a plugin that only ever looks for duplicates has a small blast radius.

Keep New, Keep Old, Keep All, or merge by hand

The four actions are the core of the plugin. Keep New sets the newly imported item as the master and merges the duplicates into it. Keep Old does the opposite and makes the existing item the master. Keep All keeps both copies, which is the escape hatch when the detection is wrong. Merge Manually hands the pair over to Zotero's own Duplicate Items Panel so you can resolve it there yourself. The master choice matters because the merge takes the union of the fields and keeps the master's identity, so the question of which copy survives determines what the attachment and note layout looks like afterwards. Two additions widen the scope. Auto-Bulk Merge, introduced in version 2.0.0, merges all duplicates in the library automatically, which is the fast path for a library that has accumulated hundreds of pairs. Show Duplicate Statistics, added in version 2.3.0, appends the duplicate count to the label of the Duplicate Items pane, so the size of the problem is visible without opening it.

The merge preserves data, and the plugin says so explicitly

The most important line in the project documentation is a callout that the plugin does not delete duplicate items arbitrarily. Instead it extracts the useful information from the duplicate items and merges them into the retained item, making improvements on the basis of Zotero's official detection and merging methods. This is the difference between a cleanup tool and a data-loss tool, and it is the reason to prefer a plugin with this shape over a script that removes rows from the database. A merge keeps the attachment, keeps the notes, keeps the tags, and keeps the fields that only existed on one of the two copies. The cost is that merges are not reversible in the way a sync is, so the sensible operational habit is to make sure the library is synced before a bulk merge, because the merged state is what will propagate to the server and to your other devices.

Non-duplicate decisions are local again since version 5.0.1

The feature with the most history is non-duplicate management. Since version 3.0.0 you can mark items as non-duplicates when Zotero flags them wrongly, and you can export and import those decisions so they transfer between devices. The export and import path was the honest part of the feature and it still works, so a decision you made can be carried to another machine by hand. The automatic part broke. Version 5.0.0 tried to sync non-duplicate pairs through Zotero.SyncedSettings with key-based storage, and the 5.0.1 hotfix had to undo it, because the sync path wrote an unsupported custom key that Zotero rejected with the error Invalid setting 'zoplicate-nonDuplicatePairs'. The hotfix disables that path, removes the legacy local synced setting on startup so Zotero stops retrying the invalid upload, and keeps non-duplicate pairs in local storage only. Cross-device syncing for these pairs is disabled until a supported storage path exists. The project also added local database schema versioning, safer cleanup for merged and deleted items, startup hydration, and a conflict-safe union merge in the 5.0.0 release, which is the kind of plumbing that matters when a plugin owns a table.

Building from source: a Node 24 toolchain and a template

The repository is a TypeScript project built on the Zotero Plugin Template, with the plugin manifest generated by zotero-plugin.config.ts and the update manifests kept in update.json and update-beta.json. The package metadata sets the addon name to Zoplicate with the addon id [email protected] and a preferences prefix of extensions.zotero.zoplicate, which tells you where the plugin stores its settings. The scripts cover the whole loop. The build command runs tsc --noEmit followed by zotero-plugin build, so type errors fail the build before anything is packaged. The test command is jest, and the lint command runs prettier and eslint. A start script runs zotero-plugin serve for development. A set of zotero: scripts drive a remote debug helper at scripts/zotero-remote-debug.sh, with start, stop, doctor, logs, screenshot, install-zotero and install-deps subcommands, plus a VNC helper pointing at a noVNC page on port 6080, and a separate beta channel of scripts against a Zotero beta build. The environment file is the place where the local machine specifics go:

bash
ZOTERO_PLUGIN_ZOTERO_BIN_PATH=/path/to/zotero
ZOTERO_PLUGIN_PROFILE_PATH=/path/to/profile
ZOTERO_PLUGIN_DATA_DIR=

The first is the Zotero binary, the second is a development profile created with the profile manager, and the third is the data directory, which may be left empty to start with the default data. The package requires Node 24 or newer.

Where the plugin stops, and what Zotero already does

The boundary is Zotero's own detection. Zoplicate does not find duplicates that Zotero's algorithm does not consider duplicates, so a pair of records with slightly different titles, years or identifiers may be missed entirely, and no merge policy fixes a detection gap. The plugin is also not a data-repair tool. It merges records that are duplicates, but it does not reconcile near-duplicates, repair broken attachments or clean up bad metadata, and the bulk merge operates on what the pane lists rather than on a fuzzy notion of similarity. The version support is the other limit. The current release targets Zotero 9 and 10, with the release manifest including Zotero 10, and the topic list and earlier changelog entries reference Zotero 6, 7 and 8, so a user on an older major is looking at an older plugin build with a different set of features, and the cross-device syncing behaviour differs between those lines. A plugin that stores its decisions in a synced setting is also, by construction, sensitive to how Zotero stores settings, which is exactly what the 5.0.1 hotfix shows.

How this differs from using Zotero's own duplicate pane

The alternative is to do nothing but use the built-in Duplicate Items pane, or to write a script against the Zotero database. The built-in pane is the reference implementation: it finds the same pairs, and merging by hand requires no third party code. What Zoplicate adds is throughput and ergonomics. Detection on import means you act on a duplicate at the moment it arrives, which is when the context is fresh, rather than discovering two years of pairs later. Auto-Bulk Merge turns a multi-hour manual cleanup into one operation. Non-duplicate marking fixes the false positives permanently instead of re-deciding them on every visit. The script approach is different again: it can be scheduled, can run over the raw SQLite database, and can do things the plugin interface does not expose, but it has to reimplement merge semantics correctly, including attachments, notes and child items, and it operates on the database underneath Zotero rather than through the application. The pragmatic split is to use the plugin for interactive work and merge decisions, and to keep Zotero's own pane as the fallback when a specific pair needs judgement the plugin cannot make.

Editorial conclusion

Adopt Zoplicate if your Zotero library accumulates duplicates from imports, web savers or PDF tools, and you want a merge that keeps the fields you care about instead of a delete. Do not rely on it for cross-device sharing of non-duplicate decisions, because version 5.0.1 disabled that sync path after it produced the Invalid setting error in Zotero, and those pairs now live only on the machine where they were made. Before you install, confirm your Zotero version is 9 or 10, since earlier majors are not covered by the current release, and read the AGPL-3.0 terms if you intend to modify the plugin rather than just run it.

Frequently asked questions

Does Zoplicate delete duplicate items when it merges them?

No. The project states that Zoplicate does not delete duplicate items arbitrarily, and instead extracts the useful information from them and merges it into the retained item, based on Zotero's official detection and merging methods.

Which Zotero versions does Zoplicate support?

Version 5.0.0 onward targets Zotero 9 and 10, with the release manifest including Zotero 10. The current release is v5.1.1 and the last push to the repository was on 2026-09-30.

Why did Zoplicate stop syncing non-duplicate pairs?

The v5.0.1 hotfix disabled the sync path that wrote an unsupported custom key to Zotero.SyncedSettings, which produced the error Invalid setting 'zoplicate-nonDuplicatePairs'. The pairs are still stored locally and can still be exported and imported by hand, but cross-device syncing is disabled until a supported storage path exists.

How do I bulk merge all duplicates in Zotero?

The Auto-Bulk Merge feature, introduced in version 2.0.0, merges all duplicate items in the library automatically. The per-item choices are Keep New, Keep Old, Keep All and Merge Manually.

What licence is Zoplicate released under?

The package metadata declares AGPL-3.0-or-later and the repository carries a LICENSE file. The plugin also uses the Zotero Plugin Template, and its homepage is chenglongma.com/zoplicate/.

Official sources

  1. ChenglongMa/zoplicate on GitHub
  2. License: AGPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chenglongma-zoplicate.svg)](https://hysenlabs.com/projects/chenglongma-zoplicate)

Was this useful?