User Scripts
A user script is a piece of JavaScript you write once and run from a
Macro. It can prompt for input, read and write
notes, call Obsidian’s own APIs, talk to other plugins, and pass values into the
rest of the macro - the escape hatch for anything QuickAdd’s built-in choices
don’t cover. This page is the full reference: how a script is shaped, how to
give it settings, what the params API offers, and a shelf of copy-paste
recipes.
You don’t need to be a professional developer, but you should be comfortable reading and lightly editing JavaScript.
Add a script to a macro
Section titled “Add a script to a macro”A user script can live in either of two places inside your vault:
- a standalone
.jsfile, or - a note (
.md) containing a```js(or```javascript) code block.
The note form is handy on mobile, where Obsidian can’t open .js files at all.
In the Macro Builder, Browse opens QuickAdd’s picker of discovered scripts
(both .js files and notes that contain a code block); it is not a native file
picker. If you add a script manually, type a .js script’s basename - for
scripts/my-script.js, enter my-script - or, for a note, type its vault path
(e.g. Scripts/my-script.md). For a specific export, append a member expression
such as my-script::start (or Scripts/my-script.md::start).
Keep a script in a note, for mobile
Section titled “Keep a script in a note, for mobile”Write your script in a ```js code block inside any note; QuickAdd runs the
first ```js (or ```javascript) block in the note and ignores all
prose and other code blocks. The block is a CommonJS module exactly like a .js
file - it must assign module.exports / exports.default (a top-level return
the way inline scripts work will not export anything):
# My script
Notes about what this does - ignored by QuickAdd.
```jsmodule.exports = async (params) => { // Your code here};```If the note has no ```js block, QuickAdd shows a notice and skips the script.
The basic shape of a script
Section titled “The basic shape of a script”Every user script must export a module with at least an entry point function:
module.exports = async (params) => { // Your code here};Or with the object syntax:
module.exports = { entry: start, settings: { name: "Script Name", author: "Your Name", options: { // Define configurable options here } }};
async function start(params, settings) { // Your code here}Use the plain function form for a quick script, and the object form when you want configurable settings in the QuickAdd UI.
What your script receives
Section titled “What your script receives”The script is called with up to two arguments: params (always) and settings
(only with the object form above).
The params object
Section titled “The params object”params carries the QuickAdd API and the Obsidian context:
{ app: App, // Obsidian app instance - see https://docs.obsidian.md/Reference/TypeScript+API/App quickAddApi: QuickAddApi, // QuickAdd API methods (documented below) variables: {}, // Variables object for sharing data between scripts and templates obsidian: obsidian, // Obsidian module with all classes and utilities abort: (message) => never // Abort macro execution with optional message}The app object provides access to the entire Obsidian API, including:
app.vault- File and folder operations (Vault API)app.workspace- Window and pane management (Workspace API)app.metadataCache- File metadata and links (MetadataCache API)app.fileManager- File operations and renaming (FileManager API)
The settings object
Section titled “The settings object”settings holds the user-configured values for your script’s options. It’s only
passed when you use the object structure with a settings block.
Let users configure your script
Section titled “Let users configure your script”User scripts can define configurable options that users set through the QuickAdd UI. This makes your scripts flexible and reusable - a folder path, an API key, or an on/off toggle becomes a form field instead of a line you have to edit.
The option types
Section titled “The option types”Text: type: "text"
Section titled “Text: type: "text"”For regular string values, paths, names, etc.
options: { "Project": { type: "text", // or "input" defaultValue: "", placeholder: "Project name", description: "Default project" // Optional: help text }}Secret: type: "secret"
Section titled “Secret: type: "secret"”For API keys, access tokens, and other sensitive values. Secrets are stored in Obsidian’s SecretStorage and QuickAdd stores only a reference in data.json.
options: { "API Key": { type: "secret", id: "api-key", // Optional stable storage id placeholder: "Paste API key", description: "Your API key" }}type: "text" / type: "input" with secret: true is still supported for older scripts and is treated as a secret setting.
Secret values are local to the Obsidian app profile. They are not included in QuickAdd package exports and are not synced through the plugin’s data.json; users must enter them on each device where the script runs.
The optional id controls the SecretStorage reference used for the setting. If omitted, QuickAdd uses the option name. Set id when you want to rename the visible setting label later without creating a new saved secret.
Toggle: type: "toggle"
Section titled “Toggle: type: "toggle"”For boolean on/off settings.
options: { "Enable Feature": { type: "toggle", // or "checkbox" defaultValue: false, description: "Enable this feature" }}Dropdown: type: "dropdown"
Section titled “Dropdown: type: "dropdown"”For choosing from predefined options.
options: { "Output Format": { type: "dropdown", // or "select" defaultValue: "markdown", options: ["markdown", "plain", "html"], description: "Choose output format" }}Format: type: "format"
Section titled “Format: type: "format"”For template strings with QuickAdd format syntax support.
options: { "File Name Template": { type: "format", defaultValue: "{{DATE}} - {{VALUE:title}}", placeholder: "Enter template", description: "Template for file names" }}Complete example
Section titled “Complete example”Here’s a comprehensive example showing all option types, and reading them back
inside start:
module.exports = { entry: start, settings: { name: "Advanced Script", author: "Your Name", options: { "API Key": { type: "secret", placeholder: "sk-...", description: "OpenAI API key" }, "Model": { type: "dropdown", defaultValue: "gpt-3.5-turbo", options: ["gpt-3.5-turbo", "gpt-4", "gpt-4-turbo"], description: "AI model to use" }, "Include Metadata": { type: "toggle", defaultValue: true, description: "Include file metadata in output" }, "Output Template": { type: "format", defaultValue: "## {{DATE:YYYY-MM-DD}}\n{{VALUE:content}}", placeholder: "Template for output", description: "Format for the output" }, "Max Results": { type: "text", defaultValue: "10", placeholder: "Number", description: "Maximum number of results" } } }};
async function start(params, settings) { const { quickAddApi, app, variables } = params;
// Access settings const apiKey = settings["API Key"]; const model = settings["Model"]; const includeMetadata = settings["Include Metadata"]; const outputTemplate = settings["Output Template"]; const maxResults = parseInt(settings["Max Results"]);
// Validate inputs if (!apiKey) { new Notice("Please configure your API key in the script settings"); throw new Error("API key not configured"); }
// Use QuickAdd API const query = await quickAddApi.inputPrompt("Enter your search query:"); if (!query) return;
// Your logic here... console.log(`Using model: ${model}`); console.log(`Max results: ${maxResults}`);
if (includeMetadata) { // Include metadata logic }
// Set variables for use in templates variables.model = model; variables.query = query; variables.resultCount = maxResults;}Prompt, format, and run other choices
Section titled “Prompt, format, and run other choices”User scripts have full access to the QuickAdd API through params.quickAddApi. For complete API documentation, see the QuickAdd API Reference. The sections below cover the calls you’ll reach for most.
Ask the user for input
Section titled “Ask the user for input”// Text inputconst text = await quickAddApi.inputPrompt("Enter text:");
// Wide text input (multi-line)const longText = await quickAddApi.wideInputPrompt("Enter description:");
// Yes/No confirmationconst confirmed = await quickAddApi.yesNoPrompt("Are you sure?");
// Suggester (dropdown selection)const choice = await quickAddApi.suggester( ["Option 1", "Option 2", "Option 3"], ["value1", "value2", "value3"]);
// Checkbox selectionconst selected = await quickAddApi.checkboxPrompt( ["Item 1", "Item 2", "Item 3"], ["Item 1"] // Pre-selected items);Share values with later steps: variables
Section titled “Share values with later steps: variables”Set variables that can be used in subsequent template operations:
// Set variablesparams.variables.myVariable = "value";params.variables.timestamp = new Date().toISOString();params.variables.results = arrayOfResults;
// Variables are accessible in templates as {{VALUE:myVariable}}Run format syntax yourself
Section titled “Run format syntax yourself”Format strings with QuickAdd syntax:
const formatted = await quickAddApi.format( "Today is {{DATE}} and the title is {{VALUE:title}}", { title: "My Document" });Run another choice
Section titled “Run another choice”Trigger other QuickAdd choices programmatically:
await quickAddApi.executeChoice("My Other Choice", { customVariable: "value"});Reach into other plugins
Section titled “Reach into other plugins”A script can talk to other Obsidian plugins through app.plugins.plugins. Check
that the plugin is there before using it:
module.exports = async (params) => { const { app } = params;
// Access Templater const templater = app.plugins.plugins["templater-obsidian"]; if (templater) { // Use Templater API }
// Access MetaEdit const metaedit = app.plugins.plugins["metaedit"]; if (metaedit) { const { update } = metaedit.api; await update("property", "value", "path/to/file.md"); }};The plugin must be installed and enabled in the vault, so check for it and fail with a clear message when it’s missing.
Handle errors and stop a macro
Section titled “Handle errors and stop a macro”Stop a macro on purpose: abort()
Section titled “Stop a macro on purpose: abort()”You can intentionally stop a macro using params.abort(). This is useful for validation or conditional execution:
module.exports = async (params) => { const { abort, quickAddApi } = params;
// Get user input const projectName = await quickAddApi.inputPrompt("Project name:");
// Validate input if (!projectName || projectName.length < 3) { abort("Project name must be at least 3 characters"); // Execution stops here - remaining macro commands won't run }
// Continue with valid input console.log(`Creating project: ${projectName}`);};When to use params.abort():
- Input validation failures
- Missing required configuration
- You want to provide a custom message after catching a
MacroAbortError - Prerequisites not met (e.g., required plugin not installed)
Prompt cancellations already throw MacroAbortError and halt macros automatically, so only call abort() in those scenarios if you need to surface a custom message or you’re stopping for a non-prompt reason.
What happens when you call abort():
- Macro execution stops immediately
- A message is logged: “Macro execution aborted: [your message]”
- Remaining commands in the macro are skipped
- No error is thrown to the user
QuickAdd API methods that can be cancelled:
inputPrompt()wideInputPrompt()yesNoPrompt()suggester()checkboxPrompt()
Each of these now rejects with MacroAbortError("Input cancelled by user") when the user presses Escape or closes the dialog. If you do nothing, the macro will automatically stop (matching user expectations). If you want to handle cancellation in your script, wrap the call in try/catch and intercept the error before it reaches the macro engine.
try { const name = await quickAddApi.inputPrompt("Your name:");} catch (error) { if (error?.name === "MacroAbortError") { // Optional custom handling (e.g., cleanup) before the macro aborts return; } throw error; // real errors should still bubble up}Important: Because cancellations now throw, you should only call abort() yourself when you want to provide a custom message or stop execution for a non-prompt reason.
module.exports = async (params) => { const { quickAddApi, abort } = params;
let name; try { name = await quickAddApi.inputPrompt("Your name:"); } catch (error) { if (error?.name === "MacroAbortError") { abort("Name is required"); return; } throw error; }
console.log(`Processing: ${name}`);};Catch unexpected errors
Section titled “Catch unexpected errors”Always include proper error handling for unexpected errors:
async function start(params, settings) { try { // Your code here const result = await riskyOperation();
if (!result) { new Notice("Operation failed", 5000); throw new Error("Failed to complete operation"); }
} catch (error) { console.error("Script error:", error); new Notice(`Error: ${error.message}`, 5000); throw error; // Re-throw to stop macro execution }}Error behavior:
- Unhandled errors in scripts automatically stop the macro
- Original error stack traces are preserved for debugging
- Errors are logged to the console for troubleshooting
Best practices
Section titled “Best practices”- Validate Settings: Always check that required settings are configured before using them
- User Feedback: Use
new Notice()to provide feedback to users - Error Messages: Provide clear, actionable error messages
- Default Values: Always provide sensible default values for options
- Documentation: Add descriptions to all options to help users understand their purpose
- Variable Names: Use clear, descriptive names for variables you set
- Async/Await: Always use async/await for asynchronous operations
- Console Logging: Use
console.log()for debugging, but remove or minimize in production
Share code between scripts
Section titled “Share code between scripts”Need the same helper functions or constants in several scripts? Put them in one
shared .js module and require() it from each script. User scripts run as
CommonJS modules and receive a require function (backed by Obsidian’s
Electron/Node require), so you can load any module on disk by its absolute
path.
Create the shared module
Section titled “Create the shared module”Save a normal .js file in your vault. (It must be a real .js file - require
loads from disk and cannot read a script written in a ```js note code block.)
Export your helpers with module.exports:
module.exports = { formatNumber: (n) => new Intl.NumberFormat("en-US").format(n), CURRENCY: "USD",};The module must already exist on disk when the requiring script runs.
Require it from your scripts
Section titled “Require it from your scripts”Build the absolute path at runtime from the vault’s base path, then require
the module. Don’t paste a literal absolute path - the vault lives at a different
location on every machine and account, so a hardcoded path breaks the moment the
vault is synced or opened elsewhere. Relative paths don’t work either (the script
has no __dirname), so always resolve against getBasePath():
module.exports = async (params) => { const { app, obsidian } = params;
// require() is desktop-only - fail clearly on mobile. if (!(app.vault.adapter instanceof obsidian.FileSystemAdapter)) { throw new Error("This script shares helpers via require(), which only works in the desktop app."); }
const path = require("path"); // getBasePath() is the supported method; older scripts may use the // equivalent app.vault.adapter.basePath. const utils = require(path.join(app.vault.adapter.getBasePath(), "scripts", "shared-utils.js"));
new obsidian.Notice(`${utils.formatNumber(1234567)} ${utils.CURRENCY}`); // "1,234,567 USD"};Cross-platform alternatives
Section titled “Cross-platform alternatives”Because require is desktop-only, on mobile (or for a portable vault) share code
another way:
- Inline the helper in each script, or
- Chain scripts in a macro and pass data (not functions) through
params.variablesbetween steps.
Advanced patterns
Section titled “Advanced patterns”Offer several actions from one script
Section titled “Offer several actions from one script”Export an object with multiple functions that users can choose from. The object can also carry plain values (a default or shared constant) alongside the functions:
module.exports = { "Create Note": createNote, "Update Note": updateNote, "Delete Note": deleteNote};
async function createNote(params) { // Implementation}
async function updateNote(params) { // Implementation}
async function deleteNote(params) { // Implementation}Return a value
Section titled “Return a value”Scripts can return values that become available as macro output:
async function start(params, settings) { const result = await processData();
// Return value becomes available as macro output return result;}Work with files
Section titled “Work with files”async function start(params, settings) { const { app, obsidian } = params;
// Get all markdown files const files = app.vault.getMarkdownFiles();
// Get a specific file const file = app.vault.getAbstractFileByPath("path/to/file.md"); if (file instanceof obsidian.TFile) { // Read file content const content = await app.vault.read(file);
// Get file metadata const metadata = app.metadataCache.getFileCache(file); const frontmatter = metadata?.frontmatter; const links = metadata?.links || []; const tags = metadata?.tags || [];
// Process content const modified = content.replace(/old/g, "new");
// Save changes await app.vault.modify(file, modified); }
// Create new file const newFile = await app.vault.create( "folder/subfolder/new-note.md", "# Title\n\nContent here" );
// Rename file await app.fileManager.renameFile( newFile, "folder/subfolder/renamed-note.md" );
// Delete file await app.vault.delete(newFile);
// Copy file await app.vault.copy( file, "path/to/copy.md" );
// Get folder const folder = app.vault.getAbstractFileByPath("folder/subfolder"); if (folder instanceof obsidian.TFolder) { // List folder contents const children = folder.children;
// Create folder if it doesn't exist const path = "new/folder/structure"; if (!app.vault.getAbstractFileByPath(path)) { await app.vault.createFolder(path); } }}Work with the active file
Section titled “Work with the active file”async function start(params, settings) { const { app, obsidian } = params;
// Get active file const activeFile = app.workspace.getActiveFile(); if (!activeFile) { new obsidian.Notice("No active file"); return; }
// Get active editor const activeView = app.workspace.getActiveViewOfType(obsidian.MarkdownView); if (activeView) { const editor = activeView.editor;
// Get selected text const selection = editor.getSelection();
// Get current line const cursor = editor.getCursor(); const line = editor.getLine(cursor.line);
// Replace selection editor.replaceSelection("New text");
// Insert at cursor editor.replaceRange( "Inserted text", cursor );
// Get entire document const fullText = editor.getValue();
// Replace entire document editor.setValue("Completely new content"); }}Work with metadata and frontmatter
Section titled “Work with metadata and frontmatter”async function start(params, settings) { const { app, obsidian } = params;
const file = app.workspace.getActiveFile(); if (!file) return;
// Get frontmatter const cache = app.metadataCache.getFileCache(file); const frontmatter = cache?.frontmatter || {};
// Update frontmatter await app.fileManager.processFrontMatter(file, (fm) => { fm.tags = fm.tags || []; fm.tags.push("processed"); fm.date = new Date().toISOString(); fm.status = "completed"; delete fm.oldField; // Remove a field });
// Get all files with specific frontmatter const filesWithTag = app.vault.getMarkdownFiles().filter(f => { const meta = app.metadataCache.getFileCache(f); return meta?.frontmatter?.tags?.includes("important"); });
// Get backlinks const backlinks = app.metadataCache.getBacklinksForFile(file);
// Get outgoing links const links = cache?.links || []; const embeds = cache?.embeds || [];}Open and navigate files
Section titled “Open and navigate files”async function start(params, settings) { const { app, obsidian } = params;
// Open file in active pane const file = app.vault.getAbstractFileByPath("path/to/note.md"); if (file instanceof obsidian.TFile) { await app.workspace.getLeaf().openFile(file); }
// Open in new pane await app.workspace.getLeaf('split').openFile(file);
// Open in new tab await app.workspace.getLeaf('tab').openFile(file);
// Open in new window await app.workspace.getLeaf('window').openFile(file);
// Navigate to specific heading await app.workspace.openLinkText( "note#heading", "", // source path true // new leaf );
// Create and open a daily note const { createDailyNote } = app.plugins.plugins["daily-notes"].instance; const dailyNote = await createDailyNote(moment()); await app.workspace.getLeaf().openFile(dailyNote);}Recipes
Section titled “Recipes”Process every note in a folder
Section titled “Process every note in a folder”async function processAllNotesInFolder(params, settings) { const { app, obsidian, quickAddApi } = params;
const folderPath = settings["Folder Path"] || "Notes"; const folder = app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof obsidian.TFolder)) { throw new Error(`Folder not found: ${folderPath}`); }
let processed = 0; const errors = [];
// Process each markdown file in folder for (const file of folder.children) { if (file instanceof obsidian.TFile && file.extension === "md") { try { const content = await app.vault.read(file);
// Your processing logic here const modified = content + "\n\n---\nProcessed: " + new Date().toISOString();
await app.vault.modify(file, modified); processed++;
} catch (error) { errors.push(`${file.path}: ${error.message}`); } } }
// Report results new obsidian.Notice(`Processed ${processed} files`); if (errors.length > 0) { await quickAddApi.infoDialog("Errors", errors); }
return { processed, errors };}Create a note from a template
Section titled “Create a note from a template”async function createNoteFromTemplate(params, settings) { const { app, quickAddApi, variables } = params;
// Get template const templatePath = settings["Template Path"]; const templateFile = app.vault.getAbstractFileByPath(templatePath);
if (!templateFile) { throw new Error(`Template not found: ${templatePath}`); }
// Read template content const template = await app.vault.read(templateFile);
// Get user input const title = await quickAddApi.inputPrompt("Note title:"); const tags = await quickAddApi.inputPrompt("Tags (comma-separated):");
// Format the template const formatted = await quickAddApi.format(template, { title: title, tags: tags.split(",").map(t => `#${t.trim()}`).join(" "), date: new Date().toISOString() });
// Create the note const fileName = `${title.replace(/[^\w\s]/gi, '')}.md`; const filePath = `${settings["Output Folder"]}/${fileName}`;
await app.vault.create(filePath, formatted);
// Open the new note const newFile = app.vault.getAbstractFileByPath(filePath); await app.workspace.getLeaf().openFile(newFile);
return filePath;}Add, remove, or replace a tag in bulk
Section titled “Add, remove, or replace a tag in bulk”async function bulkTagOperations(params, settings) { const { app, quickAddApi, obsidian } = params;
const operation = await quickAddApi.suggester( ["Add tag", "Remove tag", "Replace tag"], ["add", "remove", "replace"] );
const tag = await quickAddApi.inputPrompt("Tag name (without #):"); let newTag;
if (operation === "replace") { newTag = await quickAddApi.inputPrompt("Replace with tag:"); }
const files = app.vault.getMarkdownFiles(); let modified = 0;
for (const file of files) { await app.fileManager.processFrontMatter(file, (fm) => { fm.tags = fm.tags || [];
if (operation === "add" && !fm.tags.includes(tag)) { fm.tags.push(tag); modified++; } else if (operation === "remove") { const index = fm.tags.indexOf(tag); if (index > -1) { fm.tags.splice(index, 1); modified++; } } else if (operation === "replace") { const index = fm.tags.indexOf(tag); if (index > -1) { fm.tags[index] = newTag; modified++; } } }); }
new obsidian.Notice(`Modified ${modified} files`); return modified;}Search and replace across the vault
Section titled “Search and replace across the vault”async function searchAndReplace(params, settings) { const { app, quickAddApi, obsidian } = params;
const searchTerm = await quickAddApi.inputPrompt("Search for:"); const replaceTerm = await quickAddApi.inputPrompt("Replace with:");
const confirm = await quickAddApi.yesNoPrompt( "Confirm", `Replace all occurrences of "${searchTerm}" with "${replaceTerm}"?` );
if (!confirm) return;
const files = app.vault.getMarkdownFiles(); const results = [];
for (const file of files) { const content = await app.vault.read(file);
if (content.includes(searchTerm)) { const newContent = content.replaceAll(searchTerm, replaceTerm); await app.vault.modify(file, newContent);
const count = (content.match(new RegExp(searchTerm, 'g')) || []).length; results.push(`${file.path}: ${count} replacements`); } }
if (results.length > 0) { await quickAddApi.infoDialog("Replacements Made", results); } else { new obsidian.Notice("No matches found"); }
return results;}Enhance the daily note
Section titled “Enhance the daily note”async function enhanceDailyNote(params, settings) { const { app, obsidian } = params;
// Get or create today's daily note const { moment } = window; const dailyNotes = app.plugins.plugins["daily-notes"];
if (!dailyNotes) { throw new Error("Daily Notes plugin not enabled"); }
const { createDailyNote, getDailyNote } = dailyNotes.instance; const date = moment();
let dailyNote = getDailyNote(date, dailyNotes.instance.options); if (!dailyNote) { dailyNote = await createDailyNote(date); }
// Add content to daily note const content = await app.vault.read(dailyNote);
// Add weather (example - would need actual API) const weather = "☀️ Sunny, 22°C";
// Add tasks from yesterday const yesterday = moment().subtract(1, 'day'); const yesterdayNote = getDailyNote(yesterday, dailyNotes.instance.options);
let unfinishedTasks = ""; if (yesterdayNote) { const yesterdayContent = await app.vault.read(yesterdayNote); const taskRegex = /- \[ \] .+/g; const tasks = yesterdayContent.match(taskRegex); if (tasks) { unfinishedTasks = "\n## Carried Over Tasks\n" + tasks.join("\n"); } }
const enhanced = content + `## Weather${weather}
${unfinishedTasks}
## Notes-
## Gratitude-`;
await app.vault.modify(dailyNote, enhanced); await app.workspace.getLeaf().openFile(dailyNote);
return dailyNote.path;}Debugging tips
Section titled “Debugging tips”-
Use Console Logging Strategically
console.log("Script started", { settings, params });console.group("Processing files");files.forEach(f => console.log(f.path));console.groupEnd(); -
Developer Console Access
- Windows/Linux:
Ctrl + Shift + I - Mac:
Cmd + Option + I - Filter console by typing “QuickAdd” to see only relevant logs
- Windows/Linux:
-
Error Boundaries
try {// Risky operationawait app.vault.modify(file, content);} catch (error) {console.error("Failed to modify file:", error);new Notice(`Error: ${error.message}`);// Continue or throw depending on severity} -
Performance Monitoring
console.time("Processing files");// ... your codeconsole.timeEnd("Processing files"); -
Debugging State
// Use debugger statement to pause executiondebugger; // Execution will pause here if DevTools is open// Inspect variables at specific pointsconsole.table(variables); // Great for objects/arrays
Example scripts
Section titled “Example scripts”For complete working examples, see:
- Complete Example with All Options - Demonstrates all option types and patterns
- Movie Script - Fetches movie data from OMDb API
- Citations Manager - Integrates with Obsidian Citations plugin
- Book Finder - Searches for book information
- Migrate Dataview Properties - Migrates inline dataview properties to YAML frontmatter with configurable settings
Additional resources
Section titled “Additional resources”Obsidian API documentation
Section titled “Obsidian API documentation”- Official Obsidian API Reference
- Plugin Development Guide
- Vault Module - File operations
- MetadataCache - File metadata and links
- Workspace - Panes and leaves
- Editor - Text editing operations
QuickAdd resources
Section titled “QuickAdd resources”Troubleshooting
Section titled “Troubleshooting”Script not loading:
- Check the file path in your macro configuration
- Ensure the script exports a valid module
- Check console for syntax errors
Settings not appearing:
- Verify the settings object structure
- Ensure option names are unique
- Restart Obsidian after making changes to the settings structure
Variables not available in templates:
- Make sure you’re setting them on
params.variables - Read them with
{{VALUE:name}}, not{{name}} - Check that the script completes successfully
- See Variables and data flow for how script variables move into later Template and Capture steps
API methods returning undefined:
- Ensure you’re using
awaitwith async methods - Check that QuickAdd plugin is enabled
- Verify you’re accessing the API correctly through
params.quickAddApi