Skip to content

Instantly share code, notes, and snippets.

@nikhilweee
Last active March 26, 2026 11:49
Show Gist options
  • Select an option

  • Save nikhilweee/fdf7b471a31c2f1c2b9527c51d734d86 to your computer and use it in GitHub Desktop.

Select an option

Save nikhilweee/fdf7b471a31c2f1c2b9527c51d734d86 to your computer and use it in GitHub Desktop.
Zotero re-download missing PDFs
async function replacePDF(item) {
// filter annotations, attachments, notes
if (!item.isRegularItem()) {
return;
}
let fileExists = [];
let oldPDF = null;
// filter multiple or existing PDFs
const attachmentIDs = item.getAttachments();
for (let itemID of attachmentIDs) {
const attachment = Zotero.Items.get(itemID);
if (!attachment.isPDFAttachment()) {
continue;
}
oldPDF = attachment;
const exists = await attachment.fileExists();
fileExists.push(exists);
}
if (fileExists.length > 1) {
return; // multiple PDFs found
}
if (fileExists.pop()) {
return; // PDF already exists
}
console.log("Updating PDF for", item.getDisplayTitle());
// manually invoke "Find Available PDF"
const newPDF = await Zotero.Attachments.addAvailablePDF(item);
if (oldPDF) {
await OS.File.move(newPDF.getFilePath(), oldPDF.getFilePath());
await newPDF.eraseTx();
}
}
// loop replacePDF() over all items in our library
const libraryID = Zotero.Libraries.userLibraryID;
let items = await Zotero.Items.getAll(libraryID);
for (let item of items) {
await replacePDF(item);
}
@nikhilweee
Copy link
Copy Markdown
Author

nikhilweee commented Feb 18, 2024

This script will scan your Zotero library for missing attachments and re-download them.

To run, click on Tools > Developer > Run JavaScript and paste this code. Make sure "Run as async function" is checked.
To view console logs, click on Tools > Developer > Error Console.

Here's the blog post about this: https://nikhilweee.me/blog/2024/zotero-file-sync/

@stern-layer
Copy link
Copy Markdown

this returns an OS not defined error

@rashomon-gh
Copy link
Copy Markdown

OS.File has been deprecated by mozilla a while ago. A more compatible solution would be to use IOUtils:

async function replacePDF(item) {
  // filter annotations, attachments, notes
  if (!item.isRegularItem()) {
    return;
  }
  let fileExists = [];
  let oldPDF = null;
  
  // filter multiple or existing PDFs
  const attachmentIDs = item.getAttachments();
  for (let itemID of attachmentIDs) {
    const attachment = Zotero.Items.get(itemID);
    if (!attachment.isPDFAttachment()) {
      continue;
    }
    oldPDF = attachment;
    const exists = await attachment.fileExists();
    fileExists.push(exists);
  }
  
  if (fileExists.length > 1) {
    return; // multiple PDFs found
  }
  if (fileExists.pop()) {
    return; // PDF already exists
  }
  
  console.log("Updating PDF for", item.getDisplayTitle());
  
  // manually invoke "Find Available PDF"
  const newPDF = await Zotero.Attachments.addAvailablePDF(item);
  
  if (oldPDF && newPDF) {
    // Replaced OS.File.move with IOUtils.move
    await IOUtils.move(newPDF.getFilePath(), oldPDF.getFilePath());
    await newPDF.eraseTx();
  }
}

// loop replacePDF() over all items in our library
const libraryID = Zotero.Libraries.userLibraryID;
let items = await Zotero.Items.getAll(libraryID);

for (let item of items) {
  await replacePDF(item);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment