Feature Suggestion: Flag the Primary/Default Attachment

Let me preface this by saying that I am enjoying version 8 so far! Thank you for your work!

I would like to suggest a feature that seems to have been awaited by a few people and as early as 2013:

https://forums.zotero.org/discussion/29638/how-to-set-default-pdf-to-be-displayed-via-view-pdf
https://forums.zotero.org/discussion/83491/decide-which-child-file-to-open-by-default
https://forums.zotero.org/discussion/105392/if-i-put-more-than-one-pdf-in-an-item-how-can-i-set-a-certain-pdf-to-be-the-default-one
https://forums.zotero.org/discussion/106323/add-right-click-options-to-choose-the-primary-attachment-for-an-item-with-multiple-attachments

The feature is a way to set the primary (or default) attachment of a parent item. The primary attachment is the one opened when double clicking the parent item. The process of setting this flag could be, for example, via the right-clicking context menu or the item pane. Some visual indicator would be neat, but not essential.

Now let me conclude with a possible workaround, which I have tested once and therefore can turn out unreliable. It is based on the fact that the primary attachment seems to be the one that was added least recently to one's Zotero instance (or library?). Let me point out, that this is different to the attachment least recently added to the parent item.

So here is a possible WORKAROUND:

1. Gather all attachments under one parent item
2. Right-click each attachment and pick “Show in Finder” (MacOs) or - I assume - “Show in Explorer” (Windows)
3. Create a new parent item and add your primary attachment from finder/explorer
4. Add the remaining attachments
5. Delete the original parent item from step 1
  • There used to be at least one plugin that automated the trick to change the attachment's date to the earliest, and thus make it the default for opening, but it did not get updated for Zotero v7 let alone v8.
    https://github.com/sharpevo/zotero-pdfkit

    There is however a script for the Actions & Tags plugin that does that trick.
    https://github.com/crnkv/Zotero-Action-Scripts-Collection/blob/master/Set This as Default PDF.js

    The ability to set the default attachment to be opened has been promised.
    https://forums.zotero.org/discussion/comment/436279/#Comment_436279
  • Indeed the script you posted

    (https://github.com/crnkv/Zotero-Action-Scripts-Collection/blob/master/Set This as Default PDF.js)

    for the plugin "Actions & Tags"

    (https://github.com/windingwind/zotero-actions-tags?tab=readme-ov-file#-install)

    does the trick very efficiently. Thank you for directing me to it.

    The caveat is though that I don't know if the codes are trustworthy.

  • To note only that the above solution did not work fully for me in zotero 8.0.4. Claude gave me a working solution by updating the script, since setting the "added date" of the pdf file to be the earliest date was not enough. Here is the code I added to the Actions & Tags plugin.

    /**
    * @file Set This as Default PDF
    * @version 0.4 (Zotero 8 — URL-aware fix)
    * @usage Select one single PDF attachment, then trigger from the context menu.
    * Menu Label suggestion: Set This as Default PDF
    *
    * HOW ZOTERO PICKS THE DEFAULT PDF (priority order):
    * 1. Oldest PDF whose URL matches the parent item's URL
    * 2. Oldest PDF with any other URL
    * 3. Oldest non-PDF matching parent URL
    * 4. Oldest non-PDF with other URL
    *
    * This script wins on BOTH axes: it gives the target PDF the parent's URL
    * (priority #1), removes that URL from siblings (so they fall to priority #2),
    * AND backdates the target so it wins on age too.
    */

    const SCRIPTNAME = "Set Default PDF";

    function popup(msg, timeoutSecs = 4) {
    const pw = new Zotero.ProgressWindow({ closeOnClick: true });
    pw.changeHeadline(SCRIPTNAME);
    pw.addDescription(msg);
    pw.show();
    pw.startCloseTimer(timeoutSecs * 1000);
    }

    function showAlert(msg) {
    Zotero.alert(null, SCRIPTNAME, msg);
    }

    function getEarliestDate(attachments) {
    return attachments.reduce((earliest, att) => {
    const d = new Date(att.dateAdded + "Z");
    return d < earliest ? d : earliest;
    }, new Date());
    }

    async function setAsDefault(targetItem) {
    const parent = targetItem.parentItem;
    if (!parent) {
    showAlert("This attachment has no parent item.");
    return;
    }

    // Collect all PDF siblings
    const allAttachments = Zotero.Items.get(parent.getAttachments());
    const pdfAttachments = allAttachments.filter(
    a => a.attachmentContentType === "application/pdf"
    );

    if (pdfAttachments.length < 2) {
    showAlert("Only one PDF is attached — nothing to change.");
    return;
    }

    // Get the parent's URL (this is what Zotero matches against)
    const parentURL = parent.getField("url") || "";

    // Step 1: Give the target PDF the parent's URL so it wins priority #1
    targetItem.setField("url", parentURL);

    // Step 2: Clear the parent URL from all other PDFs so they can't compete
    for (const att of pdfAttachments) {
    if (att.id === targetItem.id) continue;
    const attURL = att.getField("url") || "";
    if (attURL === parentURL && parentURL !== "") {
    att.setField("url", "");
    await att.saveTx({ skipDateModifiedUpdate: true });
    }
    }

    // Step 3: Backdate the target to also win on age (belt AND suspenders)
    const earliestDate = getEarliestDate(pdfAttachments);
    earliestDate.setSeconds(earliestDate.getSeconds() - 1);
    const newDateString = earliestDate
    .toISOString()
    .slice(0, 19)
    .replace("T", " ");
    targetItem.setField("dateAdded", newDateString);

    await targetItem.saveTx({ skipDateModifiedUpdate: true });

    popup(`✓ "${targetItem.getField("title") || "This PDF"}" is now the default.`);
    }

    // ─── Main ─────────────────────────────────────────────────────────────────────
    (async () => {
    const theItem = (typeof item !== "undefined") ? item : null;
    if (!theItem) return; // "all items" pass — skip

    const isPDF =
    typeof theItem.isPDFAttachment === "function"
    ? theItem.isPDFAttachment()
    : theItem.attachmentContentType === "application/pdf";

    if (!isPDF) {
    showAlert("Please select a PDF attachment, not a regular library item.");
    return;
    }

    await setAsDefault(theItem);
    })();
  • Thanks for the modified script.
    For the priority order, there is also the case of locally added PDF attachments which do not have a URL. They fall in between 2 and 3:
    https://forums.zotero.org/discussion/130523/newly-added-attachment-pdf-is-set-as-primary-attachment

    I have shared a modified script on the GitHub of the Actions and Tags plugin, to limit changes only to cases when it is necessary, and indicating on the Title field of the attachment when its URL was modified:
    https://github.com/windingwind/zotero-actions-tags/discussions/602
Sign In or Register to comment.