Selection Context
When a user selects items in Zotero, the plugin builds a structured Selection Context (SelectionContext) that describes what the user selected and what type each selected item belongs to. This context serves as the input basis for the buildRequest Hook.
Selection Types
Based on the combination of selected item types, selectionContext.selectionType returns one of the following values:
| Type | Description |
|---|---|
"parent" | All selected items are parent items (top-level items) |
"child" | All selected items are child items (non-top-level items) |
"attachment" | All selected items are attachments |
"note" | All selected items are notes |
"mixed" | Selected items are a mix of multiple types |
"none" | No items are selected |
Context Structure
selectionContext = {
selectionType: "parent", // Selection type
items: {
parents: [ /* List of parent items */ ],
children: [ /* List of child items */ ],
attachments: [ /* List of attachments */ ],
notes: [ /* List of notes */ ],
},
summary: {
parentCount: 2,
childCount: 0,
attachmentCount: 0,
noteCount: 0,
},
warnings: [], // Warning messages
sampledAt: "2026-01-15T...", // Context creation time
}
Each type of item contains rich contextual information.
Parent Item (ParentContext)
A parent item is a top-level item in the Zotero library (e.g., journal article, book, web page, etc.). Each parent item context contains:
{
item: Zotero.Item, // Item object
id: number, // Item ID
title: string, // Title
attachments: [ // Child attachments under this item
{ type, filePath, mimeType, dateAdded, ... }
],
notes: [ // Child notes under this item
{ id, content, ... }
],
tags: string[], // Tag list
collections: string[], // Containing collections
children: [ // Other child items
{ id, type, ... }
],
}
Attachment (AttachmentContext)
An attachment is a file attachment of an item (PDF, Markdown, etc.). Each attachment context contains:
{
item: Zotero.Item, // Attachment item object
id: number, // Item ID
filePath: string, // Local file path
fileName: string, // Filename
mimeType: string, // MIME type (e.g., "application/pdf")
dateAdded: Date, // Date added
parentItem: { // Owning parent item
id: number,
key: string,
libraryID: number,
},
tags: string[],
collections: string[],
}
Note (NoteContext)
{
item: Zotero.Item,
id: number,
content: string, // Note content (HTML)
parentItem: { id, key, libraryID },
tags: string[],
}
Using Selection Context in Hooks
Getting Selected Attachments
export function buildRequest({ selectionContext, runtime }) {
const attachments = selectionContext.items.attachments;
return {
kind: "skillrunner.job.v1",
create: { skill_id: "my-skill" },
input: {
files: attachments.map((attachment) => ({
path: runtime.helpers.getAttachmentFilePath(attachment),
name: runtime.helpers.getAttachmentFileName(attachment),
})),
},
};
}
Getting Selected Parent Items and Their Child Content
export function buildRequest({ selectionContext, runtime }) {
const parents = selectionContext.items.parents;
for (const parent of parents) {
const title = parent.item.getField("title");
const attachments = parent.attachments; // Attachments under this parent item
const notes = parent.notes; // Notes under this parent item
}
// ...
}
Checking Selection Type to Determine Behavior
export function preflight({ selectionContext }) {
const { selectionType } = selectionContext;
if (selectionType === "none") {
// No items selected, skip
return { kind: "skip", reason: "no selected items" };
}
if (selectionType === "attachment") {
// User selected only attachments, use attachment processing logic
} else if (selectionType === "parent") {
// User selected only parent items, expand the first qualifying attachment
}
return { kind: "continue", context: { selectionType } };
}
Filtering Attachments
Use declarative validateSelection for common attachment filtering:
{
"validateSelection": {
"select": { "policy": "pdf-attachment" },
"require": {
"counts": { "attachments": 1 },
"allowMixed": false
}
}
}
For runtime-only decisions that depend on the resolved unit, use preflight to skip or continue:
export function preflight({ selectionContext, runtime }) {
const { helpers } = runtime;
const hasPdf = selectionContext.items.attachments.some(
a => helpers.isPdfAttachment(a)
);
if (!hasPdf) {
return { kind: "skip", reason: "no PDF attachments" };
}
return { kind: "continue" };
}
Workflows When No Items Are Selected
When inputs.unit: "workflow" and trigger.requiresSelection: false, the workflow can be triggered without any items selected. In this case, selectionContext.selectionType is "none", and all arrays in items are empty. This mode is suitable for creating global operations (e.g., "Create Topic Synthesis").
Declarative Selection Validation
If your workflow only needs to skip items that already have results or filter specific types of input, use the declarative validateSelection field without writing a JavaScript hook.
{
"validateSelection": {
"select": { "policy": "literature-source" },
"exclude": [
{ "kind": "generated-notes-all", "noteKinds": ["digest"] }
]
}
}
See the full documentation in Writing the Manifest.
Selection Guide: Use declarative
validateSelectionwhenever possible — it requires zero JavaScript and zero maintenance. Complex selection logic can be implemented in thebuildRequestHook.
Next Steps
- Host API Reference — Complete API for manipulating Zotero data in hooks
- Writing the Manifest — Define the workflow's input unit types