Zotero 8 for Developers
Zotero 8 includes an internal upgrade of the Mozilla platform on which Zotero is based, incorporating changes from Firefox 115 through Firefox 140.
Most of the guidance from Zotero 7 for Developers is still relevant.
Feedback
If you have questions about anything on this page or encounter other problems while updating your plugin, let us know on the dev list. Please don't post to the Zotero Forums about Zotero 8 at this time.
Dev Builds
Platform Changes
Mozilla Platform
The following list includes nearly all Mozilla changes that affected Zotero code. You may encounter other breaking changes if you use APIs not used in Zotero. Searchfox is the best resource for identifying current correct usage in Mozilla code and changes between Firefox 115 and Firefox 140.
Earlier Zotero 8.0 (formerly 7.1) beta releases were based on Firefox 128, so we've listed changes for Firefox 128 and 140 separately.
Firefox 115 → Firefox 128 ("Zotero 7.1" beta)
- Manual
Services.jsmimports must be removed (example) nsIScriptableUnicodeConverterwas removed; replaceconvertToByteArray()andconvertToInputStream()(example)nsIOSFileConstantsServicewas removed (example)nsIDOMChromeWindowwas removed (example)BrowsingContextshould be passed tonsIFilePickerinit (example, but we recommend using Zotero'sFilePickermodule instead)
Firefox 128 → Firefox 140 (Zotero 8)
- All JSMs (
.jsmfiles) in Firefox and Zotero were converted to ESMs (.mjsfiles, or.sys.mjsfiles for Firefox code) (example)- See Mozilla's primer on ESMification — in particular, the “Renamed New API” section
- Zotero includes a script that can update most JSM-based code automatically. Copy the
migrate-fx140directory into your plugin's Git repo and runmigrate-fx140/migrate.py esmify path/to/Module.jsm. To update the imports in a non-JSM file, runmigrate-fx140/migrate.py esmify --imports path/to/file.js. Both commands also accept a directory for batch conversion. - Global imports are no longer supported. Imported modules need to be assigned to a variable. (example)
- All ESMs run in strict mode
- Bluebird was removed — Zotero now uses standard JavaScript promises everywhere (example)
- Zotero provides a script that can update most Bluebird-based code automatically. Copy the
migrate-fx140directory into your plugin's Git repo and runmigrate-fx140/migrate.py asyncify path/to/file.js. This command also accepts a directory for batch conversion. - For compatibility,
Zotero.Promise.delay()andZotero.Promise.defer()are still supported.defer()can no longer be called as a constructor (example). ZoteroProtocolHandlerextensions:new AsyncChannel()now takes an async function, not a generator (example)Zotero.spawn()was removed.
Services.appShell.hiddenDOMWindowwas removed outside macOS — use only as a fallback (example)ZOTERO_CONFIGneeds to be imported (example)- Preference panes now run in their own global scope, so a
vardefined in one preference pane's script won't automatically be accessible to other preference panes. Set variables onwindowexplicitly if you need to share them between preference panes. - Update button labels using the
labelproperty, not attribute (example) - The first segment of a zotero: URI is now parsed as its host, not as part of its path (example)
Plugin Changes
Custom Menu Items
A new API allows plugins to create custom menu items in Zotero's menu popups. Plugins should use this official API if possible rather than manually injecting content.
The example below shows registering a custom menu item and a submenu in Zotero's items list context menu.
let registeredID = Zotero.MenuManager.registerMenu({ menuID: "test", pluginID: "example@example.com", target: "main/library/item", menus: [ { menuType: "menuitem", l10nID: "menu-print", onShowing: (event, context) => { Zotero.debug("onShowing"); Zotero.debug(Object.keys(context)); }, onCommand: (event, context) => { Zotero.debug("onCommand"); Zotero.debug(Object.keys(context)); }, }, { menuType: "submenu", l10nID: "menu-print", menus: [ { menuType: "menuitem", l10nID: "menu-print", onShowing: (event, context) => { Zotero.debug("onShowing submenu"); Zotero.debug(Object.keys(context)); // Only for regular items context.setVisible(context.items?.every((item) => item.isRegularItem())); }, onCommand: (event, context) => { Zotero.debug("onCommand submenu"); Zotero.debug(Object.keys(context)); }, }, ], }, ], });
Available targets:
- Main window menubar menus
- main/menubar/file — File menu in the main window menubar
- main/menubar/edit — Edit menu in the main window menubar
- main/menubar/view — View menu in the main window menubar
- main/menubar/go — Go menu in the main window menubar
- main/menubar/tools — Tools menu in the main window menubar
- main/menubar/help — Help menu in the main window menubar
- Main window library context menus
- main/library/item — Context menu for library items
- main/library/collection — Context menu for library collections
- Main window toolbar & file menu submenus
- main/library/addAttachment — “Add attachment” button/menu
- main/library/addNote — “New note” button/menu
- Main window tab context menus
- main/tab — Context menu for main window tabs
- Reader window menubar menus
- reader/menubar/file — File menu in reader window
- reader/menubar/edit — Edit menu in reader window
- reader/menubar/view — View menu in reader window
- reader/menubar/go — Go menu in reader window
- reader/menubar/window — Window menu in reader window
- Item pane context menus
- itemPane/info/row — Context menu for item pane info rows
- Notes pane add note buttons
- notesPane/addItemNote — Add item note button
- notesPane/addStandaloneNote — Add standalone note button
- Sidenav buttons
- sidenav/locate — Locate button in side navigation
More advanced options are documented in the source code.
When the plugin is disabled or uninstalled, custom menus with the corresponding pluginID will be automatically removed. If you want to unregister a custom menu manually, you can use unregisterMenu():
Zotero.MenuManager.unregisterMenu(registeredID);
If you encounter any problem with this API, please let us know on the dev list.
Upgrade Storage