Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zotero/zotero-connectors/llms.txt

Use this file to discover all available pages before exploring further.

Zotero Connectors use a Gulp-based build system orchestrated by build.sh. A single build.sh invocation copies source files from src/, runs Babel to transpile JSX, injects the correct script lists into manifests, and writes finished extension directories under build/. The same pipeline produces both the Manifest V2 Firefox build and the Manifest V3 Chromium build from one shared source tree.

Prerequisites

Before building you need:
  • Node.js 20 (the version pinned in .github/workflows/ci.yml)
  • npm (bundled with Node.js) — used to install all devDependencies
  • Git with submodule support — the repo uses nested submodules for src/translate, src/utilities, and others
The package.json lists Gulp 4, Babel 7, Puppeteer 24, Mocha 11, React 16, and several other packages as devDependencies. All of these are installed by npm install — there is nothing to install globally except Node.js.

Initial Setup

1

Clone the repository with submodules

git clone --recursive https://github.com/zotero/zotero-connectors.git
cd zotero-connectors
The --recursive flag initialises all git submodules in one step. If you already cloned without it, run init.sh (see below) to pull them in afterwards.
2

Install npm dependencies

npm install
This installs Gulp, Babel, Puppeteer, Mocha, Sinon, Chai, React, and every other tool listed in package.json.
3

(Optional) Copy config.sh

cp config.sh-sample config.sh
Edit config.sh to supply signing keys and deployment paths before doing a production build. See config.sh below for the full list of variables.
4

Run a debug build

./build.sh -d
Built extensions land in build/. The -d flag enables the translator tester and keeps code unminified.

Build Commands

build.sh accepts three option flags and can combine them freely:
FlagMeaning
-dDebug build — enables the translator tester UI, sets isDebug = true in zotero.js, skips minification
-p bBuild only the browserExt target (Chrome / Firefox / Edge)
-p sBuild only the Safari target
-v VERSIONOverride the version string injected into manifests and zotero.js (defaults to 4.999.0)
# Debug build for all browsers (recommended during development)
./build.sh -d

# Production browserExt build only
./build.sh -p b

# Production Safari build only
./build.sh -p s

# Debug build targeting only browserExt
./build.sh -d -p b

# Production build with an explicit version number
./build.sh -v 5.1.0 -p b
When neither -p b nor -p s is given, both targets are built. When TEST_CHROME=1 or TEST_FX=1 is set in the environment, build.sh automatically selects the browserExt target; TEST_SAFARI=1 selects the Safari target.

Build Output

build/
├── manifestv3/     ← Chrome / Edge (Manifest V3)
├── firefox/        ← Firefox (Manifest V2)
└── safari/         ← Safari app extension
The script starts by building a common build/browserExt/ directory, then rsyncs it into build/manifestv3/ and renames the original to build/firefox/. Each copy then receives browser-specific post-processing (icon sizes, manifest applications field removal for Chrome, etc.).

Automatic Rebuilding with gulp watch

After doing an initial build, leave a watch process running so that any file change triggers an incremental recompile:
./build.sh -d    # one-time full build
gulp watch       # incremental rebuilds on file changes
gulp watch monitors ./src/browserExt/**, ./src/common/**, ./src/safari/**, and the Google Docs integration connector sources. When a file changes it is passed through the same processFile() pipeline that the full build uses and written directly to build/.
After each rebuild you must manually reload the extension in the browser. See Running from the build directory for per-browser instructions.
A variant task gulp watch-chrome additionally calls chrome-cli to reload the Chrome extensions tab automatically.

Gulp Tasks

All Gulp logic lives in gulpfile.js. The main task is process-custom-scripts:
npx gulp process-custom-scripts --connector-version 5.1.0
npx gulp process-custom-scripts --connector-version 5.1.0 -p  # production (minify)
The processFile() stream transform handles each source file:
  • .jsx files — transpiled with @babel/plugin-transform-react-jsx and @babel/plugin-proposal-class-properties; the .jsx extension is stripped to produce .js
  • zotero_config.js — environment variable overrides for ZOTERO_GOOGLE_DOCS_DEV_MODE, ZOTERO_GOOGLE_DOCS_API_URL, ZOTERO_GOOGLE_DOCS_OAUTH_CLIENT_KEY, ZOTERO_REPOSITORY_URL, and ZOTERO_ALWAYS_FETCH_FROM_REPOSITORY are substituted at build time
  • zotero.jsisDebug and debug.log are set to true in debug builds; allowRepoTranslatorTester is set based on whether ZOTERO_REPOSITORY_URL is defined
  • schema.js — the /*ZOTERO_SCHEMA*/ placeholder is replaced with the inlined JSON from src/zotero-schema/schema.json
  • manifest.json / manifest-v3.json/*BACKGROUND SCRIPTS*/ and /*INJECT SCRIPTS*/ placeholders are replaced with the correct script arrays; the version string is injected
  • HTML files (preferences.html, progressWindow.html, etc.) — <!--BEGIN DEBUG-->…<!--END DEBUG--> blocks are stripped in production builds
After transformation every file is routed to the correct build/manifestv3/, build/firefox/, or build/safari/ directory.

The config.sh File

Copy config.sh-sample to config.sh and fill in the values you need:
FIREFOX_UPDATE_URL="https://example.com/download/connector/firefox/CHANNEL/updates.json"

# Firefox signing credentials (web-ext)
export WEB_EXT_API_KEY=""
export WEB_EXT_API_SECRET=""

# Chrome extension public key (embedded in manifest for a stable extension ID)
export CHROME_EXTENSION_KEY=""

# Deployment targets
S3_BUCKET="foo-download"
DEPLOY_HOST="deploy.local"
MANIFEST_DEPLOY_PATH="/path/to/dir"
DEPLOY_CMD="/path/to/script"

# Google Docs integration (override for development)
ZOTERO_GOOGLE_DOCS_DEV_MODE=
ZOTERO_GOOGLE_DOCS_API_URL_BETA=
ZOTERO_GOOGLE_DOCS_OAUTH_CLIENT_KEY_BETA=
build.sh sources config.sh at startup if the file exists. The file is listed in .gitignore so credentials are never committed.
The environment variables used by the Gulp build pipeline — ZOTERO_GOOGLE_DOCS_DEV_MODE, ZOTERO_GOOGLE_DOCS_API_URL, ZOTERO_GOOGLE_DOCS_OAUTH_CLIENT_KEY, ZOTERO_REPOSITORY_URL, and ZOTERO_ALWAYS_FETCH_FROM_REPOSITORY — are separate from the deployment variables in config.sh. Set them directly in your shell environment or in a wrapper script before invoking build.sh.

The init.sh Script

init.sh is a lightweight helper that sources config.sh (if present) and sets BUILD_DIR to <repo-root>/dist if it is not already defined in the environment. It is intended to be sourced by other automation scripts, not run directly, and is not a replacement for git submodule update --init --recursive. If you need to reinitialise submodules after a fresh clone (without --recursive), run:
git submodule update --init --recursive

Browser-Specific Bundle Generation with scripts/replace_browser.js

The gulpfile.js calls replaceBrowser(contents, options) from scripts/replace_browser.js when processing zotero.js. It rewrites the browser-detection comment stubs to concrete true/false assignments:
// For manifestv3 (Chrome/Edge)
replaceBrowser(contents, { browserExt: true, firefox: false, manifestV3: true })

// For Firefox
replaceBrowser(contents, { browserExt: true, firefox: true, manifestV3: false })

// For Safari
replaceBrowser(contents, { safari: true })
This is how a single src/common/zotero.js produces browser-specific values for Zotero.isFirefox, Zotero.isBrowserExt, Zotero.isManifestV3, and Zotero.isSafari at build time.

MV2 vs MV3 Build Targets

Firefox (build/firefox/)Chrome/Edge (build/manifestv3/)
Manifest version23
BackgroundEvent page (background.js, scripts array)Service worker (background-worker.js)
Source manifestsrc/browserExt/manifest.jsonsrc/browserExt/manifest-v3.json
Inject scriptsinjectIncludeBrowserExt listinjectIncludeManifestV3 list (adds virtualOffscreenTranslate.js)
Offscreen docsNot neededoffscreen/offscreen.html, offscreen/offscreenSandbox.html
The MV3 manifest also sets "minimum_chrome_version": "88" and uses the offscreen permission; the MV2 manifest sets "strict_min_version": "58.0" inside the applications.gecko block.

Running from the Build Directory

  1. Navigate to chrome://extensions/ (or edge://extensions/)
  2. Enable Developer mode
  3. Click Load unpacked and select build/manifestv3/

CI Pipeline

The GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and pull request:
- name: Set up Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'

- name: Install npm dependencies
  run: npm ci

- name: Run build script
  run: bash build.sh -p b -d

- name: Disable AppArmor for puppeteer
  run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns

- name: Run tests
  run: bash scripts/runtests.sh -i -f
The CI build uses -p b -d (debug + browserExt only) so that the test suite, which targets build/manifestv3/, always has a fresh debug build available.

Build docs developers (and LLMs) love