Injected content scripts and the background process run in completely separate JavaScript contexts. Browser APIs (Chrome/Firefox/Safari) provide a message-passing channel between them, but the raw API is cumbersome: callers must serialize arguments, route messages by string name, and manually wire up response callbacks. Zotero Connectors eliminate this boilerplate with a transparent monkey-patching approach: every method that an inject script needs to call on the background is replaced at runtime with a proxy that sends the call over the message channel and returns a promise that resolves with the response — making remote calls look identical to local ones.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.
The MESSAGES Registry
src/common/messages.js is the single source of truth for every proxied method. It exports a two-level object: the top level is a namespace (Translators, Connector, Prefs, etc.) and the second level is the method name. The value for each method controls the RPC behavior:
| Value | Meaning |
|---|---|
true | Call is forwarded to the background; a response is expected |
false | Fire-and-forget; no response is expected |
object with hook functions | Response expected, with pre/post processing on either side |
MESSAGE_SEPARATOR constant is ".". A call to Zotero.Translators.getWebTranslatorsForLocation becomes the message name "Translators.getWebTranslatorsForLocation".Hook Functions
Each method entry inMESSAGES can carry up to four hooks. They are applied in the order shown below:
| Hook | Side | When called | Purpose |
|---|---|---|---|
inject.preSend | Inject | Before the message is sent | Transform arguments before serialization (e.g. strip un-serializable objects, chunk large payloads) |
background.postReceive | Background | After the message arrives | Transform or augment arguments before calling the real function (e.g. inject the sender tab object) |
background.preSend | Background | After the function returns, before sending response | Transform the return value before serialization (e.g. strip translator code, serialize class instances) |
inject.postReceive | Inject | After the response arrives | Re-hydrate the response into proper class instances (e.g. new Zotero.Translator(data)) |
The Message Flow Step-by-Step
The following sequence applies to BrowserExt (Chrome, Firefox, Edge). Safari uses a similar pattern with a request ID for response correlation.Inject calls a proxied method
Zotero.Translators.getWebTranslatorsForLocation(url, rootURL) as if it were a local function.Monkey-patched stub fires
messaging_inject.js has replaced that function with a generated stub. The stub:- Runs
inject.preSend(args)if configured. - Calls
browser.runtime.sendMessage(["Translators.getWebTranslatorsForLocation", newArgs]).
Background receives the message
Zotero.Messaging.init() has registered a browser.runtime.onMessage listener. It calls receiveMessage("Translators.getWebTranslatorsForLocation", args, sender.tab, sender.frameId).Background executes the real function
receiveMessage resolves Zotero["Translators"]["getWebTranslatorsForLocation"], runs background.postReceive(args, tab, frameId) if present (which by default appends tab and frameId to args), then calls the real function.Background runs preSend on the response
background.preSend(response) if configured. For getWebTranslatorsForLocation this serializes the Zotero.Translator objects to plain JSON.Background returns the response
onMessage listener returns the processed response, which the browser routes back to the inject-side browser.runtime.sendMessage promise.Monkey-Patching in messaging_inject.js
src/browserExt/messaging_inject.js iterates over every entry in MESSAGES and installs the proxy at Zotero.Messaging.init() time:
Registering Custom Message Listeners
Both sides exposeZotero.Messaging.addMessageListener(name, handler) for ad-hoc messages that don’t fit the MESSAGES registry pattern:
Zotero.Messaging.sendMessage(name, args, tab, frameId):
Concrete Example: Zotero.Connector.callMethod in Inject
In an inject script, calling Zotero.Connector.callMethod("saveItems", data) looks like a direct HTTP call. In reality:
- The monkey-patched stub sends
["Connector.callMethod", ["saveItems", data]]viabrowser.runtime.sendMessage. - The background’s
receiveMessageresolvesZotero.Connector.callMethod— the real implementation insrc/common/connector.js. - The real implementation constructs an HTTP request to
http://127.0.0.1:23119/connector/saveItemsusingZotero.HTTP.request(). - The HTTP response is serialized and returned through the message channel back to the inject script.
Zotero.Connector.callMethod identically whether they run in the inject context or (hypothetically) in the background.
Large-Payload Chunking (Chromium)
Chrome’s extension message-passing protocol has a practical payload limit. When large blobs of HTML (e.g. SingleFile snapshots) must be sent from inject to background,messaging_inject.js provides sendAsChunks / getChunkedPayload:
src/common/messaging.js the background stores incoming chunks and reassembles them on demand:
saveSingleFile and ItemSaver.saveAttachmentToServer entries in MESSAGES use this pattern in their inject.preSend and background.postReceive hooks respectively.
ArrayBuffer Packing/Unpacking
Chrome’s extension messaging cannot passArrayBuffer objects (binary data for PDF attachments, images, etc.) directly. messages.js defines packArrayBuffer / unpackArrayBuffer utilities that adapt the transfer format per-platform:
| Platform | Transfer format |
|---|---|
| Firefox | Native ArrayBuffer (supported natively) |
| Safari | Base64-encoded string |
| Chrome MV3 | Array.from(new Uint8Array(arrayBuffer)) — plain byte array (with 8 MB cap) |
| Chrome MV2 | URL.createObjectURL(new Blob([arrayBuffer])) — blob URL |
API.uploadAttachment and COHTTP.request entries in MESSAGES wire these through their inject.preSend and inject.postReceive hooks:
Summary of the MESSAGES Namespaces
Translators — translator metadata & code
Translators — translator metadata & code
updateFromRemote, get, getAllForType, getWebTranslatorsForLocation, getCodeForTranslator. Most entries have preSend hooks that strip full translator code (which can be hundreds of KB) before sending over the wire.Connector — Zotero desktop HTTP client
Connector — Zotero desktop HTTP client
checkIsOnline, callMethod, callMethodWithCookies, saveSingleFile, getClientVersion, reportActiveURL, getPref. callMethod is the workhorse — it proxies any arbitrary Zotero connector HTTP endpoint.Connector_Browser — tab & UI management
Connector_Browser — tab & UI management
onSelect, onPageLoad, onTranslators, onZoteroButtonElementClick, injectScripts, injectSingleFile, isIncognito, isTabFocused, newerVersionRequiredPrompt, openTab, openConfigEditor, openPreferences, bringToFront. These are called by inject scripts to trigger UI changes and state queries that only the background can perform.ItemSaver — item & attachment saving
ItemSaver — item & attachment saving
saveAttachmentToZotero, saveStandaloneAttachmentToZotero, saveAttachmentToServer. The saveAttachmentToServer entry uses chunked payload transfer for HTML snapshot blobs.API — zotero.org cloud API
API — zotero.org cloud API
authorize, onAuthorizationComplete, clearCredentials, getUserInfo, run, uploadAttachment. uploadAttachment uses ArrayBuffer packing for binary attachment data.Prefs — extension preferences
Prefs — extension preferences
set, getAll, getDefault, getAsync, removeAllCachedTranslators, clear. set is fire-and-forget (false); all read operations return values.Messaging — meta-messaging
Messaging — meta-messaging
sendMessage (inject calls background to relay a message back to itself/another frame) and receiveChunk (chunked payload assembly). The sendMessage entry’s background.postReceive hook injects the correct tab and frameId before forwarding.GoogleDocs_API, COHTTP, Debug, Errors, Proxies, …
GoogleDocs_API, COHTTP, Debug, Errors, Proxies, …
GoogleDocs_API.onAuthComplete, run, getDocument, batchUpdateDocument), cross-origin HTTP requests (COHTTP.request), debug logging (Debug.log, Debug.get, Debug.bgInit, Debug.clear, Debug.setStore), error reporting (Errors.getErrors, Errors.getSystemInfo), proxy management (Proxies.loadPrefs, Proxies.validate, Proxies.save, Proxies.remove, Proxies.toggleRedirectLoopPrevention), connector debug counters (Connector_Debug.storing, Connector_Debug.get, Connector_Debug.count), and more.