zotero-connectors: the browser extension that turns a page into a Zotero item
Chrome, Firefox, Edge, and Safari extensions for Zotero
At a glance
- What is it?
- Zotero Connectors is the Chrome, Firefox, Edge and Safari extension that detects a scholarly page, runs a translator for it and saves the result into your library, either through a local connector server in the Zotero desktop client or through the zotero.org API. The interesting part is the four layer architecture behind that one click.
- Who is it for?
- Adopt Zotero Connectors if you already use Zotero to collect references, because the extension is the piece that turns a page into a structured item instead of a pile of notes, and the translator model means new sites can be added without touching the extension. Do not adopt it as a scraping tool in its own right, since the output is meant to land in a Zotero library and the code path runs through a translator framework rather than a general purpose extractor.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One click on a page, and a structured item appears in your library
Zotero Connectors is a browser extension for Chrome, Firefox, Edge and Safari whose whole purpose is saving a scholarly item from a web page into a Zotero library. The homepage is the Zotero download page for connectors, and the repository description is short, but the work behind it is substantial and mostly invisible to the person pressing the button.
The problem it solves is the last mile of reference collection. A page has a title, an author list, a journal, a year, a DOI and sometimes a PDF, and every one of those facts has to be moved into a bibliographic record with the right fields. Doing that by hand is the friction that makes people keep a folder of loose PDFs instead of a managed library. The connector detects the page type, finds the right translator, runs it, and hands the result to Zotero.
What is left in your browser after that is a set of carefully separated processes. The developer documentation in the README breaks the system into four components, and the message passing between them is defined by the browser rather than by Zotero, which is the constraint that shapes everything else.
Injected scripts hold the translation framework, one per page
The first component is the code injected into the individual webpage. Each page gets a full Zotero translation framework injected into it, and a Zotero.Translate.Web instance orchestrates running the individual translators, first for detection and then for translation.
That arrangement is deliberate rather than accidental. A translator has to run in the page context, because the metadata it needs is often assembled by the page's own scripts, or sits in a part of the DOM that only exists after the site has finished rendering. Running the translator in the extension's own context would mean reimplementing the site's behaviour, which is a losing game. Running it in the page means the connector inherits whatever the site does, including the parts that change.
The translation framework also provides custom classes for translator retrieval and for item saving. Those classes exist to do the things that sit outside the framework's own scope, namely fetching translator code and sending translated items onwards to either the local client or to zotero.org. In other words, the injected layer is the translator engine, and it delegates the plumbing to the background process.
The background process caches translators and does the first URL match
The second component is the background process, described as a middle layer between the translation framework in the injected scripts and Zotero itself. It does four things that are easy to underrate.
It maintains a cache of translators. It performs the initial translator detection using URL matching, so a page URL is compared against the target regular expression of each known translator, and only the translators whose pattern matches are carried forward. Those candidates are then tested more precisely by running detectWeb() in the injected scripts, which is the expensive part that needs the live page. It updates the extension user interface and kicks off translations, and it stores and retrieves connector preferences. And it is the component that sends translated items to Zotero or to zotero.org.
The list of translators and their code is fetched either from the Zotero client or from zotero.org, so the set of supported sites is not frozen in the extension. That is what makes the system extensible, and it is also why the extension needs a working connection to one of those two sources. Browser specific scripts exist for the WebExtension target and for Safari, and the project splits them accordingly.
Saving goes to the desktop client on port 23119, or to zotero.org
The third and fourth components are the two possible destinations. When Zotero is open it runs a connector HTTP server on port 23119, and the API on that server accommodates the interaction between the connectors and the client. The connector code wraps this in a Zotero.Connector.callMethod(endpoint) call, which is translated into an HTTP request to the server, so the extension code reads like a method call while the wire format is plain HTTP.
There is a constraint that is easy to miss, and the documentation states it directly. Zotero cannot interact with the connectors on its own accord, and all communication is connector initiated. The extension always makes the first move. There is no polling from the client side and no background push from the desktop application to the browser.
The fallback destination is the zotero.org web API, used when Zotero is not available. The interactions with that API are defined in a separate module in the source tree, so the two paths are cleanly separated. The practical difference is where the item ends up. Saving through the local server means the item lands in the library on your own machine and its local files, while saving through zotero.org means it goes to your online library and requires an account with the appropriate credentials.
Building it: submodules, npm install and build.sh
The connector is built from source with a small set of commands, and the README lists them in order. First you clone the repository into a directory of your choice, then you change into that directory, then you pull the top level submodules with git submodule update --init, then you install the npm dependencies, and finally you run the build script with the -d flag. Each of those is a single line, and the whole sequence fits on a screen.
The connectors end up in the build/ directory, and the README notes that the build needs only the top level submodules, so git submodule update --init without the recursive flag is enough. There is also a committed copy of the SingleFile Core bundles in lib/SingleFile, which are copied into normal builds without being rebuilt, and a separate command to regenerate them against a specific published version.
npm run update-single-file -- <version>Omitting the version uses the latest published one, while a normal build just copies what is already committed. For day to day work there is a watch mode, since the build script is run once and then gulp watch rebuilds the connectors as files change, with the caveat that you still have to reload the extension in the browser yourself. Packaging extensions from the command line has one extra step, copying config.sh-sample to config.sh and editing it.
Loading the unpacked build in Chrome and Firefox, and Safari lives elsewhere
Running the freshly built extension is a browser operation rather than a code one. In Chrome you open chrome://extensions/, enable Developer Mode, and use Load unpacked extension to select the build/browserExt directory. In Firefox you open about:debugging, click Load Temporary Add-on, and pick the build/browserExt/manifest.json file.
Safari is handled by a separate repository, the zotero/safari-app-extension project, and the README simply points there for the Safari specifics. That division is consistent with the rest of the architecture, where the Safari path uses a different extension framework and has its own background script.
The build tooling in the manifest reflects the same split. Gulp drives the build, Rollup handles bundling, Babel with the React preset handles the JSX and class properties, and Mocha with Chai and Sinon runs the tests. Puppeteer is in the dependency list, which is what lets the test suite drive a real browser, and the project also ships a .mocharc.js and a test directory. The extension UI itself is React, on React 16 with react-dom-factories.
A custom licence, a maintained main branch, and a project with no tagged releases
The licence is the one thing to read before doing anything interesting with this code. It is not MIT, Apache or GPL, and the project is published under a custom licence that GitHub cannot classify, with a LICENSE file and a separate COPYING file at the root. Anything beyond running the official extension, whether you fork it, redistribute the build or ship a modified copy, needs a reading of those files rather than an assumption.
The maintenance picture is otherwise healthy. The repository is not archived and the last push was on 2026-10-19, and the build badge shows a Travis CI pipeline on the master branch. There are no GitHub releases at all, which is normal for this kind of project because the deliverable is a browser extension distributed through the Zotero website rather than a downloadable package, and the extension is what users update, not a version of the source tree.
The dependencies tell you the age of the code. Babel is pinned to the 7.8 line, React to 16.13, and the Rollup plugins are old, while Puppeteer, Mocha and Sinon are current. That mix is what a long lived extension codebase looks like when it must keep working across Chrome, Firefox, Edge and Safari at once, and the presence of a dedicated update-single-file command suggests the team is aware of how much of the browser stack is vendored into the build.
Editorial conclusion
Adopt Zotero Connectors if you already use Zotero to collect references, because the extension is the piece that turns a page into a structured item instead of a pile of notes, and the translator model means new sites can be added without touching the extension. Do not adopt it as a scraping tool in its own right, since the output is meant to land in a Zotero library and the code path runs through a translator framework rather than a general purpose extractor. Before you build on it, read the translation architecture in the zotero/translate repository and the contributor guide, and check the port 23119 connector server path, since all communication is initiated by the extension and nothing works unless the desktop client is running and listening.
Frequently asked questions
How do I build the Zotero Connectors from source?
Clone the repository, run git submodule update --init without the recursive flag, run npm install, and then run ./build.sh -d. The connectors end up in the build/ directory, and for packaging from the command line you first copy config.sh-sample to config.sh and edit it.
How do I load the built extension in Chrome or Firefox?
In Chrome, open chrome://extensions/, enable Developer Mode, and use Load unpacked extension to select the build/browserExt directory. In Firefox, open about:debugging, click Load Temporary Add-on, and select the build/browserExt/manifest.json file. Safari is handled by the separate zotero/safari-app-extension repository.
How does the Zotero connector send a saved item to the desktop client?
When Zotero is open it runs a connector HTTP server on port 23119, and the extension's Zotero.Connector.callMethod(endpoint) calls are translated into HTTP requests to that server. All communication is connector initiated, so the desktop client never reaches out to the extension on its own.
What happens to a saved item when the Zotero desktop app is not running?
Item saving falls back to the zotero.org web API, with the interactions defined in a separate module in the source tree. That means the item goes to your online library rather than to the local client and its local files.
Under what licence is Zotero Connectors released?
It is not one of the standard open source licences. The project is published under a custom licence that GitHub cannot classify, and the root of the repository contains both a LICENSE file and a COPYING file, so the terms need to be read directly before forking, redistributing or modifying the code.
Official sources
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.
[](https://hysenlabs.com/projects/zotero-zotero-connectors)Was this useful?