When a translator finishes extracting bibliographic metadata from a web page, that data begins a second journey: it must be packaged, enriched with browser cookies for authentication-gated attachments, and delivered either to the Zotero desktop client over its local HTTP server or — when the client is unavailable — to zotero.org via the Web API. This pipeline is split across two files: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.
src/common/itemSaver.js runs in the inject (content) script context where it has direct DOM access, and src/common/itemSaver_background.js runs in the background script context where it can make arbitrary network requests and issue connector API calls.
Overview: Two-Phase Save
Inject-side (itemSaver.js)
The
ItemSaver class is constructed in the inject script. Its saveItems() method first attempts _saveToZotero(). If that fails with status 0 (desktop client offline), it falls back to _saveToServer().Package items and add cookies
The payload is assembled: translated items, a session ID, the current page URI, and the active proxy.
callMethodWithCookies() retrieves all browser cookies for the tab’s URL and attaches them as detailedCookies in the request body so the desktop client can authenticate attachment downloads.Send to connector server
Zotero.Connector.callMethodWithCookies("saveItems", payload) POSTs the items to the Zotero desktop client at http://localhost:23119/connector/saveItems.ItemSaver Constructor Options
sessionID ties together item creation, attachment uploads, and progress polling so that the desktop client can associate all parts of a save into one session.
Save Flow to Zotero Desktop
saveItems(items, attachmentCallback, itemsDoneCallback)
Entry point. Calls _saveToZotero() and catches status === 0 to trigger the server fallback:
Attachment Filtering
Before sending,_saveToZotero() filters item attachments based on preferences retrieved from the connector:
automaticSnapshots— iffalse,text/htmlattachments are dropped.downloadAssociatedFiles— iffalse, non-HTML attachments are dropped.- PDF pages (
document.contentType === 'application/pdf') are automatically appended as aFull Text PDFattachment.
Saving Attachments
The connector supports two attachment workflows depending on the connected Zotero version:Zotero handles downloads (legacy)
When
supportsAttachmentUpload is false, HTML snapshots are sent to the desktop client via _executeSingleFile() (which posts to the saveSingleFile endpoint). Zotero handles other attachment downloads itself. The connector calls _pollForProgress() to relay status updates back to the progress window.Connector uploads directly (modern)
When
supportsAttachmentUpload is true, the background page fetches each PDF or EPUB as an ArrayBuffer and posts it to saveAttachment with binary data in the request body and metadata in the X-Metadata header.Attachment Delivery Methods
Zotero.ItemSaver.saveAttachmentToZotero(attachment, sessionID, tab)
Defined in itemSaver_background.js. Downloads the attachment as an ArrayBuffer and POSTs it directly to the Zotero desktop client:
=?UTF-8?Q?...?=) so they can be safely transmitted in HTTP headers.
Zotero.ItemSaver.saveStandaloneAttachmentToZotero(attachment, sessionID, tab)
Same pattern as saveAttachmentToZotero, but calls the saveStandaloneAttachment endpoint and does not require a parentItemID. Used when intercepting direct PDF/file downloads via contentTypeHandler.js. Default timeout is 60 seconds.
Zotero.ItemSaver.saveAttachmentToServer(attachment, tab)
Uploads an attachment to zotero.org when the desktop client is offline. The workflow is:
_createServerAttachmentItem(attachment)— creates an attachment item viaZotero.API.createItem(), receives an item key.- Binary data for HTML snapshots is encoded as UTF-8; Safari fetches binary attachments in the content script (where the user’s cookies are present) and passes them as base64.
Zotero.API.uploadAttachment(attachment)— posts to the Web API file upload endpoint.
Snapshot Saving via SingleFile
HTML snapshots are captured using the SingleFile library (src/common/singlefile.js). The inject-side method _executeSingleFile() calls:
retrievePageData() injects the single-file-hooks-frames.js script into the page (for deferred image loading support) and then calls singlefile.getPageData(). Fetch requests within SingleFile are handled by Zotero.SingleFile.singleFileFetch(), which throttles concurrent requests (capped at 10 via throttleAsync) to avoid crashing the extension on pages with many resources.
Cookie Handling
Browser cookies are critical for downloading authentication-gated attachments (journal PDFs, institutional repository files, etc.).callMethodWithCookies() collects all cookies for the tab URL using browser.cookies.getAll() and serialises them into a detailedCookies string:
Firefox with First-Party Isolation enabled may return no cookies if
firstPartyDomain is not specified. The connector sets firstPartyDomain: null for Firefox ≥ 59, which may result in attachment downloads failing when FPI is active.Bot-Detection Bypass
For whitelisted domains (sciencedirect.com, pdf.sciencedirectassets.com, ncbi.nlm.nih.gov), the background page can attempt to bypass JavaScript-based bot protection via two escalating strategies:
- Hidden iframe — loads a monitor page in a hidden
<iframe>within the current tab and waits up to 5 seconds for the PDF URL to appear in awebRequestevent. - Popup window — opens a centred browser popup where the user can manually solve a CAPTCHA, then monitors for the PDF download URL.
Progress Polling: _pollForProgress()
When using the legacy attachment workflow, the inject script polls the sessionProgress endpoint every second (up to 60 polls) to relay attachment download status back to the progress window:
