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.
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.
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.
Services.jsm imports must be removed (example)nsIScriptableUnicodeConverter was removed; replace convertToByteArray() and convertToInputStream() (example)nsIOSFileConstantsService was removed (example)nsIDOMChromeWindow was removed (example)BrowsingContext should be passed to nsIFilePicker init (example, but we recommend using Zotero's FilePicker module instead).jsm files) in Firefox and Zotero were converted to ESMs (.mjs files, or .sys.mjs files for Firefox code) (example)migrate-fx140 directory into your plugin's Git repo and run migrate-fx140/migrate.py esmify path/to/Module.jsm. To update the imports in a non-JSM file, run migrate-fx140/migrate.py esmify --imports path/to/file.js. Both commands also accept a directory for batch conversion.migrate-fx140 directory into your plugin's Git repo and run migrate-fx140/migrate.py asyncify path/to/file.js. This command also accepts a directory for batch conversion.Zotero.Promise.delay() and Zotero.Promise.defer() are still supported. defer() can no longer be called as a constructor (example).ZoteroProtocolHandler extensions: new AsyncChannel() now takes an async function, not a generator (example)Zotero.spawn() was removed.Services.appShell.hiddenDOMWindow was removed outside macOS — use only as a fallback (example)ZOTERO_CONFIG needs to be imported (example)var defined in one preference pane's script won't automatically be accessible to other preference panes. Set variables on window explicitly if you need to share them between preference panes.label property, not attribute (example)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:
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.