QuickAdd API
The QuickAdd API is a set of JavaScript methods you can call from macros, user scripts, and inline scripts. Reach for it when a workflow needs logic a choice can’t express on its own: ask the user a question, run another choice, call an AI model, or read values already in your vault.
| Module | What it does |
|---|---|
| User input | Ask for text, pick from a list, confirm, or collect several answers at once |
| Choice execution | Run another choice, or apply a template to the current note |
| Utility | Read and set the clipboard, read the editor’s selection |
| Date | Format today, tomorrow, yesterday, or any day offset |
| AI | Send prompts, build tool-calling agents, get structured output |
| Field suggestions | List the values a property already has across your vault |
Accessing the API
Section titled “Accessing the API”The API can be accessed in several ways:
From QuickAdd Scripts (Macros/User Scripts)
Section titled “From QuickAdd Scripts (Macros/User Scripts)”module.exports = async (params) => { const { quickAddApi, app, variables } = params; // Use quickAddApi here};From Other Plugins/Scripts
Section titled “From Other Plugins/Scripts”const quickAddApi = app.plugins.plugins.quickadd.api;// Use the API methodsFrom Templater Scripts
Section titled “From Templater Scripts”<%*const quickAddApi = app.plugins.plugins.quickadd.api;const result = await quickAddApi.inputPrompt("Enter value:");tR += result;%>User Input Methods
Section titled “User Input Methods”requestInputs(inputs: Array<{ id: string; label?: string; type: "text" | "number" | "textarea" | "dropdown" | "date" | "field-suggest" | "suggester" | "slider"; placeholder?: string; defaultValue?: string; numericConfig?: { min?: number; max?: number; step?: number }; sliderConfig?: { min: number; max: number; step?: number }; options?: string[]; dateFormat?: string; description?: string; optional?: boolean; suggesterConfig?: { allowCustomInput?: boolean; caseSensitive?: boolean; multiSelect?: boolean; } }>): Promise<Record<string, string>>
Section titled “requestInputs(inputs: Array<{ id: string; label?: string; type: "text" | "number" | "textarea" | "dropdown" | "date" | "field-suggest" | "suggester" | "slider"; placeholder?: string; defaultValue?: string; numericConfig?: { min?: number; max?: number; step?: number }; sliderConfig?: { min: number; max: number; step?: number }; options?: string[]; dateFormat?: string; description?: string; optional?: boolean; suggesterConfig?: { allowCustomInput?: boolean; caseSensitive?: boolean; multiSelect?: boolean; } }>): Promise<Record<string, string>>”Opens a one-page modal to collect multiple inputs in one go. Values already present in variables are used and not re-asked. Returned values are also stored into variables.
Behavior:
- Uses existing values for any ids that already exist in
variables(including empty strings). - Prompts only for missing (
undefined/null) inputs. - If the user closes the modal without submitting, the promise rejects with
MacroAbortError("Input cancelled by user").
Field Types:
text: Single-line text inputnumber: Numeric input, optionally bounded bynumericConfigtextarea: Multi-line text inputdropdown: Fixed dropdown menu (no search, must select from list)date: Date input with natural language support (short aliases liket,tm,ydare supported and configurable in settings)field-suggest: Vault field suggestions (uses{{FIELD:...}}syntax)slider: Bounded numeric input with a slider and editable number field. RequiressliderConfig.minandsliderConfig.max;sliderConfig.stepdefaults to1. Invalid slider configs fall back tonumber.suggester: NEW - Searchable autocomplete with custom options (allows custom input)- Supports multi-select mode via
suggesterConfig.multiSelect: truefor comma-separated selections
- Supports multi-select mode via
requestInputs always returns text values in a Record<string, string>. For a multi-select suggester input, the selected items are stored as one comma-separated string, not as selected record objects.
Example:
const values = await quickAddApi.requestInputs([ { id: "project", label: "Project", type: "text", defaultValue: "Inbox" }, { id: "due", label: "Due", type: "date", dateFormat: "YYYY-MM-DD" }, { id: "confidence", label: "Confidence", type: "slider", defaultValue: "50", sliderConfig: { min: 0, max: 100, step: 5 } }, { id: "status", label: "Status", type: "dropdown", options: ["Todo","Doing","Done"] }, { id: "tags", label: "Tags", type: "suggester", options: ["work", "personal", "urgent", "review"], placeholder: "Type to search tags...", suggesterConfig: { caseSensitive: false } }]);Suggester with Dynamic Options (e.g., from Dataview):
// Get dynamic options from Dataviewconst dv = app.plugins.plugins.dataview?.api;const projectNames = dv?.pages() .where(p => p.type === "project") .map(p => p.file.name) .array() ?? ["Inbox"];
const values = await quickAddApi.requestInputs([ { id: "project", label: "Select Project", type: "suggester", options: projectNames, placeholder: "Start typing project name..." }]);Multi-Select Suggester:
// Select multiple tags, comma-separatedconst values = await quickAddApi.requestInputs([ { id: "tags", label: "Select Tags", type: "suggester", options: ["#work", "#personal", "#project", "#urgent", "#review"], suggesterConfig: { multiSelect: true, caseSensitive: false }, placeholder: "Type or select multiple tags..." }]);
// Result: values.tags = "#work, #project, #urgent"// Split into array if needed:const tagArray = values.tags.split(', ').filter(Boolean);inputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean, imagePaste?: { sourcePath?: string } }): Promise<string>
Section titled “inputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean, imagePaste?: { sourcePath?: string } }): Promise<string>”Opens a prompt that asks for text input.
Parameters:
header: The prompt title/questionplaceholder: (Optional) Placeholder text in the input fieldvalue: (Optional) Default valueoptions.cursorAtEnd: (Optional) Whentrue, places the caret after the default value instead of selecting itoptions.imagePaste: (Optional) Accept clipboard-image paste: the image is saved as a vault attachment (per Obsidian’s attachment settings) and an embed link is inserted at the caret. Clipboard text always wins over an image. Only enable this when the value flows into note content - an embed link in a file name or path would corrupt it.sourcePathis the note path the link will live in when known; omit it to get vault-root links that resolve from anywhere.
Returns: Promise resolving to the entered string.
Cancellation: If the user cancels or presses Escape, the promise rejects with MacroAbortError("Input cancelled by user"). Letting it bubble will stop the macro automatically. Catch it only if your script wants to handle the cancellation itself.
Example:
try { const name = await quickAddApi.inputPrompt( "What's your name?", "Enter your full name", "John Doe" ); console.log(`Hello, ${name}!`);} catch (error) { if (error?.name === "MacroAbortError") { // Optional: perform cleanup before QuickAdd aborts the macro return; } throw error;}wideInputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean }): Promise<string>
Section titled “wideInputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean }): Promise<string>”Opens a wider prompt for longer text input (multi-line).
Parameters: Same as inputPrompt
Returns: Promise resolving to the entered string. Cancelling rejects with MacroAbortError (same as inputPrompt).
Keyboard: Pressing Tab inserts a tab character at the cursor (handy for nested Markdown lists) instead of moving focus; with text selected, Tab indents every line the selection touches. Shift+Tab still moves focus out of the field. See multi-line input.
Example:
const description = await quickAddApi.wideInputPrompt( "Project Description", "Enter a detailed description...", "This project aims to...");yesNoPrompt(header: string, text?: string): Promise<boolean>
Section titled “yesNoPrompt(header: string, text?: string): Promise<boolean>”Opens a confirmation dialog with Yes/No buttons.
Parameters:
header: The dialog titletext: (Optional) Additional explanation text
Returns: Promise resolving to true (Yes) or false (No). If the user closes the dialog without answering, the promise rejects with MacroAbortError.
Example:
const confirmed = await quickAddApi.yesNoPrompt( "Delete Note?", "This action cannot be undone.");
if (confirmed) { // Proceed with deletion}infoDialog(header: string, text: string[] | string): Promise<void>
Section titled “infoDialog(header: string, text: string[] | string): Promise<void>”Shows an information dialog with an OK button.
Parameters:
header: Dialog titletext: Single string or array of strings (for multiple lines)
Example:
await quickAddApi.infoDialog( "Operation Complete", [ "Files processed: 10", "Errors: 0", "Time taken: 2.5 seconds" ]);suggester(displayItems: string[] | Function, actualItems: any[], placeholder?: string, allowCustomInput?: boolean, options?: { renderItem?: (value: any, el: HTMLElement) => void }): Promise<any>
Section titled “suggester(displayItems: string[] | Function, actualItems: any[], placeholder?: string, allowCustomInput?: boolean, options?: { renderItem?: (value: any, el: HTMLElement) => void }): Promise<any>”Opens a single-selection prompt with searchable options. Can optionally allow custom input not in the predefined list.
quickAddApi.suggester(...) is single-select only. It returns one selected actualItems value, or a custom string when allowCustomInput is enabled. For multiple selections, use checkboxPrompt(...) for a list of string options, or requestInputs(...) with a type: "suggester" input and suggesterConfig.multiSelect: true. Those multi-select alternatives return strings/text values rather than the selected record objects that quickAddApi.suggester(...) can return, so map the returned strings back to your records if needed.
Parameters:
displayItems: Array of display strings OR a map functionactualItems: Array of actual values to return (strings or objects)placeholder: (Optional) Placeholder text shown in the suggesterallowCustomInput: (Optional) Whentrue, allows users to enter custom text not inactualItems. Defaults tofalseoptions.renderItem: (Optional) Custom renderer(value, el) => voidto control how each suggestion row is drawn
Returns: Promise resolving to the selected value or custom input. Cancelling rejects with MacroAbortError.
Examples:
Basic usage:
const fruit = await quickAddApi.suggester( ["🍎 Apple", "🍌 Banana", "🍊 Orange"], ["apple", "banana", "orange"]);With placeholder text:
const fruit = await quickAddApi.suggester( ["🍎 Apple", "🍌 Banana", "🍊 Orange"], ["apple", "banana", "orange"], "Choose your favorite fruit");With map function:
const files = app.vault.getMarkdownFiles();const selectedFile = await quickAddApi.suggester( file => file.basename, // Display just the filename files // Return the full file object);Complex objects:
const tasks = [ { id: 1, title: "Task 1", priority: "high" }, { id: 2, title: "Task 2", priority: "low" }];
const selectedTask = await quickAddApi.suggester( task => `${task.title} (${task.priority})`, tasks, "Select a task to edit");Allow custom input (like quick switcher):
// User can select from existing tags or type a new oneconst existingTags = ["#project", "#personal", "#work"];const selectedTag = await quickAddApi.suggester( existingTags, existingTags, "Select existing tag or type new one", true // allowCustomInput = true);// Returns either an existing tag or the user's custom inputCustom rendering
Section titled “Custom rendering”You can take full control over how suggestions are rendered by providing options.renderItem. The function receives the actual item value and the suggestion row element. If the renderer throws, QuickAdd falls back to default rendering.
Custom-rendered list (no custom input):
// Shows a color dot + subtitle; stores result in variables.projectmodule.exports = async (params) => { const { quickAddApi } = params;
const items = [ { name: 'Inbox', path: 'Projects/Inbox.md', status: 'Active' }, { name: 'Roadmap', path: 'Projects/Roadmap.md', status: 'Paused' }, { name: 'Backlog', path: 'Projects/Backlog.md', status: 'Done' }, ];
const values = items.map(i => i.name); const meta = Object.fromEntries(items.map(i => [i.name, i]));
const selected = await quickAddApi.suggester( values, // display items values, // actual items 'Pick a project', false, // allowCustomInput { renderItem: (value, el) => { const info = meta[value] || {}; const row = el.createEl('div'); const title = row.createEl('div', { cls: 'qa-title' });
const dot = title.createEl('span'); dot.setAttr('style', 'display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:8px;' + `background:${statusColor(info.status)};`);
title.createEl('span', { text: value });
const sub = row.createEl('div', { text: info.path || '' }); sub.setAttr('style', 'font-size:12px;color:var(--text-muted);'); } } );
if (!selected) return; params.variables.project = selected; return selected;
function statusColor(status) { switch (status) { case 'Active': return 'var(--text-accent)'; case 'Paused': return 'orange'; case 'Done': return 'var(--interactive-accent)'; default: return 'var(--text-muted)'; } }};Allow custom input with tailored rendering:
// Highlights when value is new; stores result in variables.projectmodule.exports = async (params) => { const { quickAddApi } = params; const values = ['Inbox', 'Roadmap', 'Backlog'];
const selected = await quickAddApi.suggester( values, values, 'Type a new project or pick…', true, // allowCustomInput { renderItem: (value, el) => { const isNew = !values.includes(value); const row = el.createEl('div');
const title = row.createEl('div', { text: value }); title.setAttr('style', 'font-weight:600;');
const hint = isNew ? 'New project (press Enter to create)' : 'Existing project'; const sub = row.createEl('div', { text: hint }); sub.setAttr('style', 'font-size:12px;color:var(--text-muted);'); } } );
if (!selected) return; params.variables.project = selected; return selected;};checkboxPrompt(items: string[], selectedItems?: string[]): Promise<string[]>
Section titled “checkboxPrompt(items: string[], selectedItems?: string[]): Promise<string[]>”Opens a checkbox prompt allowing multiple selections.
Parameters:
items: Array of options to displayselectedItems: (Optional) Array of pre-selected items
Returns: Promise resolving to an array of selected item strings.
Example:
const features = await quickAddApi.checkboxPrompt( ["Dark Mode", "Auto-save", "Spell Check", "Line Numbers"], ["Auto-save", "Line Numbers"] // Pre-selected);
console.log("Enabled features:", features);Choice Execution
Section titled “Choice Execution”executeChoice(choiceName: string, variables?: {[key: string]: any}): Promise<void>
Section titled “executeChoice(choiceName: string, variables?: {[key: string]: any}): Promise<void>”Executes another QuickAdd choice programmatically. This is a one-way trigger: it passes variables into the target choice, waits for that choice to finish, and resolves with undefined. It does not return data from the target choice to the caller. After the target choice finishes, QuickAdd clears the temporary variable map used by that API execution. If you call it from inside a Macro script, do not expect the caller’s current params.variables values to still be available afterward unless you saved or restored them yourself.
For the Macro data-flow implications, see executeChoice is a trigger.
Parameters:
choiceName: Name of the choice to executevariables: (Optional) Variables to pass to the choice
Example:
// Execute a template choice with variablesawait quickAddApi.executeChoice("Create Meeting Note", { meetingTitle: "Project Review", attendees: "John, Jane, Bob", date: "2024-01-15", value: "Main agenda content" // Special: maps to {{VALUE}}});Batch processing example:
const contacts = [ { name: "John Doe", email: "john@example.com", company: "ACME" }, { name: "Jane Smith", email: "jane@example.com", company: "Tech Corp" }];
for (const contact of contacts) { await quickAddApi.executeChoice("Create Contact", { contactName: contact.name, contactEmail: contact.email, contactCompany: contact.company });}applyTemplateToActiveFile(templatePath: string, options?: { mode?: "cursor" | "top" | "bottom" | "replace" }): Promise<TFile | null>
Section titled “applyTemplateToActiveFile(templatePath: string, options?: { mode?: "cursor" | "top" | "bottom" | "replace" }): Promise<TFile | null>”Applies a template to the active note without creating a new file. The template runs through the full QuickAdd format pipeline ({{title}} and the unnamed {{VALUE}}/{{NAME}} resolve to the note’s basename), and Templater syntax is processed. See Apply Template to Note for the full behavior, including frontmatter merging.
Parameters:
templatePath: Vault path to the template fileoptions.mode: (Optional) How to apply the template. Defaults to"replace"for empty notes and"bottom"otherwise."cursor": Insert at the cursor position (requires the note to be open in the active editor)"top": Insert below the note’s frontmatter"bottom": Append to the end of the note"replace": Replace the entire note content
Returns: The target file, or null if nothing was applied (e.g., no active markdown note).
Example:
// Apply a meeting template to the currently open noteawait quickAddApi.applyTemplateToActiveFile("templates/meeting.md", { mode: "top"});Utility Module
Section titled “Utility Module”Access via quickAddApi.utility:
getClipboard(): Promise<string>
Section titled “getClipboard(): Promise<string>”Gets the current clipboard contents.
Example:
const clipboardText = await quickAddApi.utility.getClipboard();console.log("Clipboard contains:", clipboardText);setClipboard(text: string): Promise<void>
Section titled “setClipboard(text: string): Promise<void>”Sets the clipboard contents.
Example:
await quickAddApi.utility.setClipboard("Hello, World!");getSelection(): string
Section titled “getSelection(): string”Gets the currently selected text in the active editor. Returns an empty string if there is no active editor or no selection.
Example:
const selection = quickAddApi.utility.getSelection();if (selection) { console.log("Selected:", selection);}Combined example:
// Transform clipboard contentsconst original = await quickAddApi.utility.getClipboard();const transformed = original.toUpperCase();await quickAddApi.utility.setClipboard(transformed);Date Module
Section titled “Date Module”Access via quickAddApi.date:
now(format?: string, offset?: number): string
Section titled “now(format?: string, offset?: number): string”Gets formatted current date/time.
Parameters:
format: (Optional) Moment.js format string, defaults to “YYYY-MM-DD”offset: (Optional) Day offset (negative for past, positive for future)
Examples:
// Current dateconst today = quickAddApi.date.now(); // "2024-01-15"
// Custom formatconst timestamp = quickAddApi.date.now("YYYY-MM-DD HH:mm:ss");
// With offsetconst nextWeek = quickAddApi.date.now("YYYY-MM-DD", 7);const lastMonth = quickAddApi.date.now("YYYY-MM-DD", -30);tomorrow(format?: string): string
Section titled “tomorrow(format?: string): string”Shorthand for now(format, 1).
yesterday(format?: string): string
Section titled “yesterday(format?: string): string”Shorthand for now(format, -1).
Example:
const yesterdayLog = `Daily Notes/${quickAddApi.date.yesterday()}.md`;const tomorrowTask = `Tasks for ${quickAddApi.date.tomorrow("dddd, MMMM D")}`;AI Module
Section titled “AI Module”Access via quickAddApi.ai:
prompt(prompt: string, model: string | {name: string, provider?: string}, settings?: object): Promise<object>
Section titled “prompt(prompt: string, model: string | {name: string, provider?: string}, settings?: object): Promise<object>”Sends a prompt to an AI model and returns the response.
Parameters:
prompt: The prompt textmodel: Model identifier. The model must be configured in Settings → QuickAdd → AI → Providers. Accepts:- A model name string, e.g.
"gpt-4o". Resolves to the first provider that serves it. - A provider-qualified string, e.g.
"openai/gpt-4o", where the prefix is a provider’s stable ID (shown in the provider’s edit form) or display name. Use this when two providers serve the same model name. If a provider literally serves a model whose id IS the whole string (OpenRouter’sopenai/gpt-4o, for example), that literal model wins - use the object form to override. - An object, e.g.
{name: "gpt-4o", provider: "openai"}. Withproviderset, the lookup is scoped to exactly that provider and cannot be shadowed by literal slash-named models.
- A model name string, e.g.
settings: (Optional) Configuration object:variableName: Output variable name (default: “output”)shouldAssignVariables: Auto-assign to variables (default: false)modelOptions: Model parameters (temperature, max_tokens, etc.)showAssistantMessages: Show AI responses in UI (default: true)systemPrompt: Override system prompt
Returns: Object with:
[variableName]: The AI response[variableName]-quoted: The response in markdown quote format
Example using string:
const result = await quickAddApi.ai.prompt( "Summarize this text: " + noteContent, "gpt-4", { variableName: "summary", modelOptions: { temperature: 0.3, max_tokens: 150 } });
console.log(result.summary);// Use in template: {{VALUE:summary}}Example using Model object:
const result = await quickAddApi.ai.prompt( "What is the capital of France?", {name: "gpt-4"}, { variableName: "capital", shouldAssignVariables: true, modelOptions: { temperature: 0.6, max_tokens: 60 } });Note: For newer models like gpt-4o or custom provider models, add them in Settings → QuickAdd → AI → Providers. Some providers support auto-sync to automatically update available models.
chunkedPrompt(text: string, promptTemplate: string, model: string | {name: string, provider?: string}, settings?: object): Promise<object>
Section titled “chunkedPrompt(text: string, promptTemplate: string, model: string | {name: string, provider?: string}, settings?: object): Promise<object>”Splits text into chunks, runs promptTemplate once per chunk, and joins the
results. Use this for inputs that are too large for a single request.
Parameters:
text: The full input text to process.promptTemplate: The prompt run for each chunk. Reference the current chunk with{{value:chunk}}.model: Model identifier (bare or provider-qualified string, or{name, provider?}object), as forprompt.settings: (Optional) Configuration object:variableName: Output variable name (default: “output”)shouldAssignVariables: Auto-assign to variables (default: false)modelOptions: Model parameters (temperature, max_tokens, etc.)showAssistantMessages: Show AI responses in UI (default: true)systemPrompt: Override system promptchunkSeparator:RegExpused to splittext(default:/\n/)chunkJoiner: String inserted between chunk results (default:"\n")shouldMerge: Merge small adjacent chunks up to the budget (default:true)maxChunkTokens: Maximum estimated tokens for each chunk’s text (the{{value:chunk}}portion only - the system prompt and prompt template are budgeted separately). Token counts are estimated locally; values above the model’s estimated input budget are capped automatically.
Behavior:
- Chunk sizes are estimated locally (QuickAdd no longer bundles model-specific tokenizers); the configured provider remains the source of truth for exact limits.
- A chunk larger than the budget is split near a natural boundary (paragraph, sentence, or space) so it still fits - including when
shouldMergeisfalse. - If the provider still rejects a prompt because the input exceeds its context window, that chunk is split and retried automatically. Output/completion-budget and quota errors are surfaced as-is (splitting cannot fix them).
- The prompt template is rendered through the formatter once per chunk (plus once for sizing), so side-effectful tokens such as
{{MACRO:…}}run on every render - avoid them inside achunkedPrompttemplate.
Returns: Object with [variableName] (joined results) and [variableName]-quoted.
const result = await quickAddApi.ai.chunkedPrompt( longText, "Summarize this section:\n{{value:chunk}}", "gpt-4o", { variableName: "summary", chunkJoiner: "\n\n" });Give the model tools to call: ai.agent(config)
Section titled “Give the model tools to call: ai.agent(config)”Introduced in QuickAdd 2.14.0.
Build an agent: give the model a prompt and a set of tools (JS functions), and it will call them in a bounded multi-step loop until it has an answer. Works across your configured OpenAI-compatible, Anthropic, and Gemini providers.
const agent = quickAddApi.ai.agent({ model: "gpt-5", system: "You manage an Obsidian vault. Use the tools to ground your answers.", tools: { // built-in tools, opt-in (see ai.tools.* below) ...quickAddApi.ai.tools.vault({ only: ["read_note", "search_notes"] }), // your own tool save_link: quickAddApi.ai.tool({ description: "Append a URL to the reading-list note.", inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"], }, needsApproval: true, // ask before running (the model chose the args) execute: async ({ url }) => { const file = app.vault.getAbstractFileByPath("Reading list.md"); await app.vault.append(file, `\n- ${url}`); return { saved: true }; }, }), }, maxSteps: 12, // optional; default 20, hard-capped at 100});
const { text, steps, toolCalls } = await agent.generate({ prompt: "Summarise my notes about {{VALUE:topic}} and save any links you find.", assignToVariable: "summary", // optional: writes {{VALUE:summary}} for a later step});ai.agent(config) returns an Agent. Config:
model- a configured model: bare name ("gpt-4o"), provider-qualified string ("openai/gpt-4o"), or{ name, provider? }object, as forai.prompt.system- system prompt (defaults to your AI Assistant default system prompt).tools- an object map of tool name → tool (fromai.tool()and/orai.tools.*).toolChoice-"auto"(default) |"none"|"required"|{ type: "tool", toolName }.stopWhen- one or more stop conditions fromai.stepCountIs(n)/ai.hasToolCall(name).maxSteps- step budget (default 20, hard cap 100). Sugar forstopWhen: ai.stepCountIs(n).maxOutputTokens,modelOptions- passed to the provider.
agent.generate(options) runs the loop and resolves to a result:
text- the final assistant text.object- present only when you pass aschema(structured output, below).steps- the full transcript:{ text, toolCalls, toolResults, finishReason }[].toolCalls/toolResults- from the last step (input/outputfields, AI-SDK style).usage-{ inputTokens, outputTokens, totalTokens }.finishReason-"stop" | "max-steps" | "length" | "aborted" | "context-overflow".
Options: prompt (formatted, like ai.prompt), schema, system/toolChoice/maxOutputTokens
(per-call overrides), and assignToVariable (write text into {{VALUE:name}}).
The agent is a stateless config holder - each generate() is independent (no retained
conversation). Reuse means reusing the config; run one generate() at a time per agent.
ai.tool(def)
Section titled “ai.tool(def)”Declares a tool. def: { description, inputSchema (JSON Schema), execute, needsApproval?, readOnly?, strict? }.
inputSchemais a JSON-Schema subset (type/properties/required/enum/items). Unsupported keywords (pattern,additionalProperties,$ref,format, …) are rejected at registration so a provider can’t silently drop a constraint.execute(input, ctx)runs your code with the model-choseninput(validated against the schema first). Return a string (used verbatim) or any JSON-serialisable value.needsApproval(boolean or(args) => boolean) asks before running.readOnly: truemarks a tool that only reads, so it auto-runs under the default confirmation setting.
⚠️ Security. Tool handlers run with the same full privilege as your script (Node
require,app, the vault). The model decides which tool to call and with what arguments, possibly influenced by note content it reads (indirect prompt injection). QuickAdd never runs model-chosen arguments through the formatter - and neither should you: never pass a tool’sinputtoquickAddApi.format(),eval, a shell, orfetchwithout validating it. Never put secrets in a tool’s description or arguments (they are sent to the provider). Confirmation is governed by each tool’sneedsApprovalplus the global Confirm AI tool calls setting (default destructive only).
Ready-made tools: ai.tools.{vault, workspace, system}(options)
Section titled “Ready-made tools: ai.tools.{vault, workspace, system}(options)”Opt-in groups of ready-made tools. Each returns a tool map you spread into an agent’s tools.
Options: { only, exclude, prefix, allowedRoots } (allowedRoots confines a group to the listed
folders).
| Group | Read-only (auto-run) | Write (asks for approval) |
|---|---|---|
vault | read_note, list_notes, search_notes, get_property_values | create_note, append_to_note, insert_under_heading |
workspace | get_active_note, get_selection | - |
system | get_date | - |
Write tools sanitise every model-chosen path (rejecting traversal and config dirs like .obsidian/
.git, and symlinks that escape the vault), fail rather than overwrite an existing note, and are
frontmatter-aware. There are no ambient tools - nothing runs unless you spread it into tools.
allowedRoots confines both groups it applies to. For vault it bounds the paths the model may
read or write. For workspace it bounds which file’s content the agent can pull in: get_active_note
returns active:null and get_selection returns an empty string whenever the currently-open file
lives outside the roots, so a note you fenced off can’t be surfaced into the transcript by a
model steered through injected content. (system ignores it - get_date touches no files.) An
absent or all-blank allowedRoots is vault-wide, the default. Note that confinement scopes what
these tools expose; it is not a sandbox for an untrusted agent - your own script’s require/
fetch/quickAddApi.utility.* remain ambient - so only hand a group to an agent you trust with the
folders you grant it.
Get a validated object back: agent.generate({ prompt, schema })
Section titled “Get a validated object back: agent.generate({ prompt, schema })”Pass a JSON schema to get a validated object back:
const { object } = await quickAddApi.ai.agent({ model: "gpt-5" }).generate({ prompt: "Extract the title and tags from the selection.", schema: { type: "object", properties: { title: { type: "string" }, tags: { type: "array", items: { type: "string" } } }, required: ["title", "tags"], },});// object => { title: "...", tags: ["...", "..."] }object is the parsed, schema-validated result (or undefined if the model could not produce a
match after one repair attempt). Structured output works on current models - OpenAI GPT-5.x (and
GPT-4o-class), Anthropic Claude 4.x, and Gemini 3.x; it can be combined with tools. Older models
that do not support schema-constrained output (e.g. legacy OpenAI chat models) reject the request
outright with a provider error - use a current model rather than expecting a best-effort fallback.
getModels(): string[]
Section titled “getModels(): string[]”Returns available AI models.
Example:
const models = quickAddApi.ai.getModels();const selectedModel = await quickAddApi.suggester(models, models);getMaxTokens(model: string): number
Section titled “getMaxTokens(model: string): number”Gets the maximum token limit for a model.
estimateTokens(text: string): number
Section titled “estimateTokens(text: string): number”Introduced in QuickAdd 2.14.0.
Estimates the token count for text using QuickAdd’s provider-agnostic estimator. This is useful for rough prompt sizing, but the configured AI provider remains the source of truth for exact context limits and usage.
Example:
const text = await quickAddApi.utility.getClipboard();const tokenCount = quickAddApi.ai.estimateTokens(text);
if (tokenCount > 4000) { await quickAddApi.infoDialog( "Text Might Be Too Long", `The text is estimated at ${tokenCount} tokens.` );}countTokens(text: string, model: string | {name: string}): number
Section titled “countTokens(text: string, model: string | {name: string}): number”Compatibility alias for estimateTokens. The model argument (a model name or
object) is accepted for existing scripts but ignored, since QuickAdd no longer
bundles model-specific tokenizers. Prefer estimateTokens(text).
getRequestLogs(limit?: number): Array<object>
Section titled “getRequestLogs(limit?: number): Array<object>”Returns recent in-memory AI request logs (newest first).
What each entry includes:
id: unique request id (also shown in console lifecycle logs)createdAt: timestampprovider,endpoint,modelsystemPrompt,prompt(the final sent text)modelOptionsstatus:pending | success | errordurationMsusage(when available)errorMessage(when failed)
Notes:
- Logs are stored in memory only (not persisted to vault files).
- QuickAdd keeps up to 25 completed entries.
- If more than 25 requests are still in-flight (
pending), those pending entries are kept until they finish, then older completed entries are trimmed.
Example:
const logs = quickAddApi.ai.getRequestLogs(10);const latest = logs[0];console.log(latest?.id, latest?.status, latest?.model);getLastRequestLog(): object | null
Section titled “getLastRequestLog(): object | null”Returns the latest AI request log entry, or null if none exist.
getRequestLogById(id: string): object | null
Section titled “getRequestLogById(id: string): object | null”Returns a specific AI request log entry by id.
Example:
const last = quickAddApi.ai.getLastRequestLog();if (!last) return;
const sameRequest = quickAddApi.ai.getRequestLogById(last.id);console.log(sameRequest?.prompt);clearRequestLogs(): void
Section titled “clearRequestLogs(): void”Clears all in-memory AI request logs.
Complete Example: Research Assistant
Section titled “Complete Example: Research Assistant”Here’s a comprehensive example combining multiple API features:
module.exports = async (params) => { const { quickAddApi, app, variables } = params;
try { // Get research parameters const topic = await quickAddApi.inputPrompt("Research Topic:"); if (!topic) return;
const sources = await quickAddApi.checkboxPrompt( ["Web Search", "Academic Papers", "Books", "Videos"], ["Web Search", "Academic Papers"] );
// AI-assisted outline generation const { outline } = await quickAddApi.ai.prompt( `Create a research outline for: ${topic}`, "gpt-4", { variableName: "outline", shouldAssignVariables: true, modelOptions: { temperature: 0.7 } } );
// Create folder structure const folder = `Research/${topic}`;
// Check if folder exists before creating try { const folderExists = await app.vault.adapter.exists(folder); if (!folderExists) { await app.vault.createFolder(folder); } } catch (err) { console.error(`Failed to create folder: ${err}`); new Notice(`Failed to create research folder: ${err.message}`); return; }
// Set variables for templates variables.topic = topic; variables.sources = sources.join(", "); variables.date = quickAddApi.date.now("YYYY-MM-DD HH:mm"); variables.outline = outline;
// Execute template choice await quickAddApi.executeChoice("Research Template", variables);
// Show completion await quickAddApi.infoDialog( "Research Project Created", [ `Topic: ${topic}`, `Sources: ${sources.length} selected`, `Location: ${folder}`, "AI outline generated successfully" ] );
} catch (error) { console.error("Research assistant error:", error); new Notice(`Error: ${error.message}`); }};Error Handling Best Practices
Section titled “Error Handling Best Practices”Always wrap API calls in try-catch blocks:
module.exports = async (params) => { const { quickAddApi } = params;
try { const input = await quickAddApi.inputPrompt("Enter value:");
if (!input) { // User cancelled - handle gracefully return; }
// Process input...
} catch (error) { console.error("Script error:", error);
await quickAddApi.infoDialog( "Error", `An error occurred: ${error.message}` ); }};Performance Tips
Section titled “Performance Tips”- Batch Operations: Use loops wisely to avoid overwhelming the system
- Debounce User Input: Add delays between rapid operations
- Check File Existence: Verify files exist before operations
- Validate Input: Always validate user input before processing
Field Suggestions Module
Section titled “Field Suggestions Module”Access via quickAddApi.fieldSuggestions:
getFieldValues(fieldName: string, options?: object): Promise<string[]>
Section titled “getFieldValues(fieldName: string, options?: object): Promise<string[]>”Retrieves all unique values for a specific field across your vault.
Parameters:
fieldName: The name of the field to search foroptions: (Optional) Filtering options:folder: Only search in a specific folder (e.g., “daily/notes”)folders: Only search in any of these folders (OR filter)tags: Only search in files with all of these tags (AND filter)includeInline: Include Dataview inline fields (default: false)includeInlineCodeBlocks: Include inline fields inside specific fenced code block types whenincludeInlineis true (e.g.,["ad-note"])
Returns: Promise resolving to sorted array of unique field values
Examples:
Basic usage:
// Get all status values in vaultconst statuses = await quickAddApi.fieldSuggestions.getFieldValues("status");const selected = await quickAddApi.suggester(statuses, statuses);With folder filter:
// Get project types only from projects folderconst projectTypes = await quickAddApi.fieldSuggestions.getFieldValues( "type", { folder: "projects" });With multiple folder filters:
// Get relation types from either goals or projectsconst relationTypes = await quickAddApi.fieldSuggestions.getFieldValues( "type", { folders: ["goals", "projects"] });With tag filter:
// Get priorities from files tagged with both #work and #importantconst priorities = await quickAddApi.fieldSuggestions.getFieldValues( "priority", { tags: ["work", "important"] });Include inline fields:
// Get all client names including inline fieldsconst clients = await quickAddApi.fieldSuggestions.getFieldValues( "client", { folder: "work/projects", includeInline: true });Include inline fields in specific code block types:
const ids = await quickAddApi.fieldSuggestions.getFieldValues( "Id", { folder: "work/projects", includeInline: true, includeInlineCodeBlocks: ["ad-note"] });clearCache(fieldName?: string): void
Section titled “clearCache(fieldName?: string): void”Clears the field suggestions cache for better performance.
Parameters:
fieldName: (Optional) Specific field to clear, or clears all if omitted
Example:
// Clear cache for specific field after bulk updatesawait quickAddApi.fieldSuggestions.clearCache("status");
// Clear entire cacheawait quickAddApi.fieldSuggestions.clearCache();Complete Field Management Example
Section titled “Complete Field Management Example”module.exports = async (params) => { const { quickAddApi, app, variables } = params;
// Smart task creation with field suggestions const taskType = await quickAddApi.suggester( await quickAddApi.fieldSuggestions.getFieldValues("type", { folder: "tasks" }), await quickAddApi.fieldSuggestions.getFieldValues("type", { folder: "tasks" }) );
const priority = await quickAddApi.suggester( ["🔴 High", "🟡 Medium", "🟢 Low"], await quickAddApi.fieldSuggestions.getFieldValues("priority") || ["high", "medium", "low"] );
const assignee = await quickAddApi.suggester( await quickAddApi.fieldSuggestions.getFieldValues("assignee", { tags: ["person"], includeInline: true }), await quickAddApi.fieldSuggestions.getFieldValues("assignee", { tags: ["person"], includeInline: true }) );
// Create task with consistent field values variables.type = taskType; variables.priority = priority; variables.assignee = assignee;
await quickAddApi.executeChoice("Create Task", variables);};See Also
Section titled “See Also”- Macro Choices - Using scripts in macros
- Inline Scripts - Using scripts in templates
- Format Syntax - Template variables
- Examples - Practical implementations