This is an old revision of the document!


We’re in the process of updating the documentation for Zotero 5.0. Some documentation may be outdated in the meantime. Thanks for your understanding.

Zotero JavaScript API

Whereas Zotero's Server API allows read and write access to online Zotero libraries, it is also possible to access the local Zotero client through the JavaScript API. (It is also possible to directly access the local SQLite database, but that approach is much more fragile.)

Note that the (mostly user-contributed) documentation of the JavaScript API is not comprehensive. If you use the JavaScript API in ways beyond what's described here, please consider expanding this wiki page.

Helpful Resources

For this section, you’ll likely find the following helpful:

An Easy Debugging Environment

Various Firefox extensions create servers within Firefox that expose the internal Firefox environment, including Zotero internals. These can be used as shells for easy experimentation. These include:

  • Plain Old Webserver (POW) - Zotero Browser is an example of getting POW to interact with Zotero to create rich annotated bibliographies.
  • MozRepl - interactive shell, available by telnetting to localhost on port 4242 (by default). Linux and Mac OS X users can use the built in telnet client. Windows users should use Putty. There is also a Firefox plugin to provide MozRepl access directly inside Firefox.
    • Potential MozRepl Problems: Longer scripts can time out non-deterministically. A solution to this is to either send the code to the terminal application in smaller sized chunks, or to keep pasting the code in until it works. Use an external library (like CPAN's MozRepl module) for finer grained control. Syntax errors can cause silent failure. Avoid this by avoiding excessively long blocks of procedural code - factor everything into smaller functions wherever possible.

Perl programmers should be aware of the MozRepl CPAN module, and the closely related MozRepl::RemoteObject which allows you to access JavaScript objects from inside Perl programs.

The Zotero Object

Zotero exposes an object-oriented JavaScript API that can be used to access and modify Zotero data. Within Zotero itself, most SQL statements are limited to the data layer, with all other elements (including the entire UI) using the data API to access the data. Through the base Zotero XPCOM service, this functionality is available to all privileged code in Firefox, including other loaded extensions.

XPCOM

A common problem in developing Mozilla extensions is figuring out how to pass data between different windows, which have their own scopes and do not by default share the same variables and code. While there are various mechanisms to get around this, the recommended method, and the method used by Zotero, is to use an XPCOM singleton component to store common data, which can then be accessed from any window that imports the component. (XPCOM is the cross-platform component object model—the framework—that forms the basis for the Mozilla application environment.)

The base Zotero service is an XPCOM component written in JavaScript. While normally XPCOM components implement various predefined interfaces themselves, in Zotero the data layer and most of the core functionality are stored within a JavaScript object that is then stuffed into the special wrappedJSObject property of the component. The component itself does not define any XPCOM interfaces. (This is vaguely a hack, but it addresses our needs sufficiently and is an approach used by several other extensions.) Chrome overlays and windows in Zotero import the core object via the script include.js, which calls getService() on the component and assigns the wrapped object to the variable Zotero. Zotero methods can then be called from anywhere within the window’s scope simply by calling, for example, var item = Zotero.Items.get(1).

Access to the Zotero service is not limited to Zotero itself, however. As a standard XPCOM component, the Zotero service—and, specifically, the wrapped JavaScript object—can be accessed from anywhere within privileged code, including other loaded extensions.

To access the data API in your own extension, you will need access to the core Zotero JavaScript object. If your extension operates within the main browser overlay, you already have access to the Zotero object and don’t need to take further steps. Otherwise, you can import the Zotero object into other windows either by including the script chrome://zotero/content/include.js within a XUL file (recommended) or by manually calling getService() directly on the Zotero XPCOM service and assigning the wrapped JS object to a variable.

chrome://zotero/content/include.js:

var Zotero = Components.classes["@zotero.org/Zotero;1"]
				.getService(Components.interfaces.nsISupports)
				.wrappedJSObject;

Notification System

Zotero has a built-in notification system that allows other privileged code to be notified when a change is made via the data layer—for example, when an item is added to the library. Within Zotero itself, this is used mostly to update the UI when items change, but external extensions can use the system to perform additional operations when specific events occur—say, to sync item metadata with a web-based tool.

Available events:

  • add (c, s, i, t, ci, it)
  • modify (c, s, i, t)
  • delete (c, s, i, t)
  • remove (ci, it)
  • move (c, for changing collection parent)

c = collection, s = search (saved search), i = item, t = tag, ci = collection-item, it = item-tag

See the Zotero Sample Plugin for a demonstration.

API Methods

The Zotero JavaScript API is under-documented, and at present requires a lot of looking around in the source code. The most useful parts of the source code are in chrome/content/zotero/xpcom and xpcom/data, and the chrome/content/zotero (particularly zoteroPane.js and fileInterface.js).

Once you have access to the core Zotero object, you can use the objects and methods provided by the Zotero JavaScript API.

Zotero uses a combination of instantiatable objects (e.g. Zotero.Item = function(){…}) and singletons (Zotero.Items = new function(){…}). The most important example of the former is Zotero.Item, which represents a single item in the database. Singletons are used mostly to group related static methods into a single namespace.

Adding items and modifying data

A typical operation might include a call to Items.get() to retrieve an Item instance, calls to Item methods on the retrieved object to modify data, and finally a save() to save the modified data to the database.

var creator = new Zotero.Creator;
creator.firstName = 'William';
creator.lastName = 'Shakespeare';
creator.save();
 
var item = new Zotero.Item;
item.setType(Zotero.ItemTypes.getID('book'));
item.setField('title', 'Much Ado About Nothing');
item.setCreator(0, creator, 'author');
var itemID = item.save();
// Fetch saved items with Items.get(itemID)
var item = Zotero.Items.get(itemID);
 
alert(item.getField('title')); // "Much Ado About Nothing"
 
alert(item.getCreator(0)); // {'firstName'=>'William', 'lastName'=>'Shakespeare',
                           //   'creatorTypeID'=>1, 'fieldMode'=>0}
item.setField('place', 'England');
item.setField('date', 1599);
item.save(); // update database with new data

Create new Zotero object

This is the first thing that you need to do when interacting with Zotero's internals. The code to do so is:

var Zotero = Components.classes["@zotero.org/Zotero;1"].getService(Components.interfaces.nsISupports).wrappedJSObject;

Get the Zotero Pane to interact with the Zotero GUI

var ZoteroPane = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow("navigator:browser").ZoteroPane;

Then grab the currently selected items from the zotero pane:

//get first selected item
var selected_items = ZoteroPane.getSelectedItems();
var item = selected_items[0];
 
// proced if selected item is neither a collection nor a note
if ( ! item.isCollection() & ! item.isNote()) {
    if (item.isAttachment()) {
        // find out about attachment
    }
    if (item.isRegularItem()) {
        // we could grab attachments:
        // var att_ids = item.getAttachments(false);
        // if (att_ids.length>1) exit(); // bailout
        // item_att=Zotero.Items.get(att_ids[0]);
    }
    alert(item.id);
}

If you are focused on data access, then the first thing you will want to do will be to retrieve items from your Zotero. Creating an in-memory search is a good start.

    var search = new z.Search(); 

Search for items containing a specific tag

Starting with the code from “Setup a Zotero search” we then use the following code to retrieve items with a particular tag:

    search.addCondition('tag', 'is', 'tag name here');

Zotero Collection Operations

Get the collection tree and display as a series of nested ordered lists

This code was developed in the Firefox extension Plain Old Webserver server side JavaScript. Note that it's a recursive function. With a bit of jQuery the nested ordered list can be easily transformed into a tree widget.

var Zotero = Components.classes["@zotero.org/Zotero;1"] .getService(Components.interfaces.nsISupports).wrappedJSObject;
 
var render_collection = function(coll) {
    if (!coll) { 
        coll = null; 
    }
    var collections = Zotero.getCollections(coll);
    document.writeln("<ul>");
    for (c in collections) {
        document.writeln('<li>' + '<a href="view_collection.sjs?name=' + encodeURI(collections[c].name) + '&id=' + collections[c].id + '">' + collections[c].name  + '</a></li>');
        if (collections[c].hasChildCollections) {
	   var childCol = render_collection(collections[c].id);
        }
    }   
    document.writeln("</ul>");
}
 
render_collection();

Get the items for a particular collection

var Zotero = Components.classes["@zotero.org/Zotero;1"] .getService(Components.interfaces.nsISupports).wrappedJSObject;
var collectionid = pow_server.GET.id; // or some other way of finding the collectionID here
var collection = z.Collections.get(collectionid);
var items = collection.getChildItems();
// or you can obtain an array of itemIDs instead:
var itemids = collection.getChildItems(true);

Create a New Collection in a function

async function createCollection(name) {
    var currentCollection = ZoteroPane.getSelectedCollection();
    var collection = new Zotero.Collection();
    collection.name = name;
    collection.parentID = currentCollection.id;
    var collectionID = await collection.saveTx();
    return collectionID;
}

This function creates a new collection with a given name and makes it a subcollection of the currently selected one. Since the saving will result in a Promise object, we add the await before the function call and make sure that this is within an async function.

Zotero Search Basics

var s = new Zotero.Search();
s.addCondition('joinMode', 'any'); // joinMode defaults to 'all' as per the 
                                        // advanced search GUI

To add the other conditions available in the advanced search GUI, use the following:

s.addCondition('recursive', 'true');  // equivalent of "Search subfolders" checked
s.addCondition('noChildren', 'true'); //               "Only show top level children
s.addCondition('includeParentsAndChildren', 'true');   "Include parent and child ..."

There are also some “hidden” search conditions around line 1735 of chrome/content/zotero/xpcom/search.js in the Zotero source code (although some should only be used internally). [TODO remove mention of source code and enumerate all conditions]

One example is:

s.addCondition('deleted', 'true');  // Include deleted items

Search by collection

To search for a collection or a saved search you need to know the ID:

s.addCondition('collectionID', 'is', collectionID);
s.addCondition('savedSearchID', 'is', savedSearchID);

Search by tag

To search by tag, you use the tag text:

    var tagname = 'something';
    search.addCondition('tag', 'is', tagname);

Search by other fields

The complete list of other fields available to search on is on the search fields page.

Once the search conditions have been set up, then it's time to execute the results:

var results = search.search();

This returns the item ids in the search as an array [I could be wrong … ]. The next thing to do is to get the Zotero items for the array of IDs:

var items = z.Items.get(results);

Managing citations and bibliographies

TODO: this is pretty sparse. the rtfscan code is a good place to look for some guidance.

Getting a bibliography for an array of items:

Here we use Zotero's Quick Copy functions to get a bibliography in the style specified in Zotero's preferences.

First we start with a list of as in the previous entry.

  var qc = z.QuickCopy;
  var biblio = qc.getContentFromItems(new Array(item),
                                      z.Prefs.get("export.quickCopy.setting"));
    var biblio_html_format = cite.html;
    var biblio_txt         = cite.text; 

Get a list of available styles

      var styles = zotero.Styles.getVisible();
      var style_info = [];
      for each ( var s in styles) {
           style_info.push( { "id" : s.styleID, "name" : s.title } );
       }
      JSON.stringify(style_info); // e.g. to return json from the mozrepl

TODO: get citations. change the style. get stuff in other formats, especially RTF

Get information about an item.

TODO: need to list all the possible fields here, and what kind of entry they belong to.

To get an item's abstract, we get the 'abstractNote' field from the Zotero item:

    var abstract = item.getField('abstractNote'); 

Get child notes for an item

To get the child notes for an item, we use the following code:

     var notes = item.getNotes(); 

This returns an array of notes. Each note is in HTML format. To get each note in turn we just iterate through the array:

    for (var j=0;j<notes.length;j++) {
        var note = z.Items.get(notes[j]);
        var note_html = note.getNote();
    } 

This technique works for anything that can have related items attached within the Zotero database. This includes items and notes.

  var related_items = item.relatedItemsBidirectional 

This returns a list of items just like in the search examples.

Get an Item's Attachments

Here's some example code to get the full text of HTML and PDF items in storage and puts the data in an array:

var item = 'some item' ; // some Zotero Item obtained previously
var fulltext = new Array;
if (item.isRegularItem()) { // not an attachment already
    var attachments = selected_items[item].getAttachments(false);
    for (a in attachments) {
        var a_item = Zotero.Items.get(attachments[a]);
        if (a_item.attachmentMIMEType == 'application/pdf'
            || a_item.attachmentMIMEType == 'text/html') {
            fulltext.push(a_item.attachmentText);
        }
    }
}

To Do

  • Select a Zotero saved search
  • Combining search terms
  • Complete list of search operators
  • Complete list of search fields - with description of what the more obscure fields mean - e.g. abstractNote for abstract, and how do we search the fulltext archive?
  • fulltext for an item
  • Get stored attachments for an item

Batch Editing

The JavaScript API can provide a powerful way to script changes to your Zotero library. The common case of search-and-replace is accomplished easily using a basic script.

First, install the Execute JS Firefox extension to interact with Zotero via JavaScript. (Advanced users can also use the Firefox Browser Console or Scratchpad in Browser mode after enable chrome/add-on debugging in the Web Console settings.) Back up your database first, and temporarily disable auto-sync in the Sync pane of the Zotero preferences.

In Execute JS, switch the target window to an open browser window, paste the relevant code into the “JS-Code to execute” box, make any necessary changes, and click “Execute”.

Example: Item Field Changes

Edit the first three lines as necessary:

var fieldName = "publicationTitle";
var oldValue = "Foo";
var newValue = "Foo2";
 
var fieldID = Zotero.ItemFields.getID(fieldName);
var s = new Zotero.Search;
s.addCondition(fieldName, 'is', oldValue);
var ids = s.search();
if (ids) {
	for(var i in ids) {
		var item = Zotero.Items.get(ids[i]);
		var mappedFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(item.itemTypeID, fieldName);
		item.setField(mappedFieldID ? mappedFieldID : fieldID, newValue);
		item.save();
	}
	alert(ids.length + " items updated");
}
else {
	alert("No items found");
}

The list of field names to use can be retrieved via the server API: https://api.zotero.org/itemFields?pprint=1.

Example: Delete All Automatic Tags

var tagType = 1; // automatic
Zotero.Tags.erase(Object.keys(Zotero.Tags.getAll([tagType])))

Example: Delete Tags By Name

var tags = ["foo", "bar", "baz"];
var ids = [];
var allTags = Zotero.Tags.search();
tags = tags.map(tag => tag.toLowerCase());
for (var id in allTags) {
    if (tags.indexOf(allTags[id].name.toLowerCase()) != -1) {
      ids.push(id);
    }
}
Zotero.Tags.erase(ids);

Example: Delete Tags By Part of Name

var tags = ["foo", "bar", "baz"];
var ids = [];
tags.forEach(function (tag) {
    ids = ids.concat(Object.keys(Zotero.Tags.search(tag)));
});
Zotero.Tags.erase(ids);