This is the full developer documentation for QuickAdd
# Getting Started
> Get started with QuickAdd: install it, pick between Template, Capture, Macro, and Multi choices, and build your first workflow
QuickAdd turns your repetitive Obsidian actions - creating a note from a template, logging a line to your journal, running a script - into single commands you trigger with a hotkey. Set a workflow up once, then run it in a keystroke from anywhere in your vault.
New here? Build your [first workflow](#first-workflow) below in about a minute.
## Install QuickAdd
[Section titled “Install QuickAdd”](#install-quickadd)
Install QuickAdd from Obsidian’s Community Plugins browser, then enable it.
If you cannot use the plugin browser, follow the [manual installation guide](/docs/ManualInstallation/).
## Choose the right choice type
[Section titled “Choose the right choice type”](#choose-the-right-choice-type)
| If you want to… | Use this | Start here |
| ---------------------------------------------------------- | --------------- | -------------------------------------------------- |
| Create a new note from a reusable file | Template choice | [Template Choices](/docs/Choices/TemplateChoice/) |
| Append text to a journal, log, task list, or existing file | Capture choice | [Capture Choices](/docs/Choices/CaptureChoice/) |
| Run one or more Obsidian commands, scripts, or choices | Macro choice | [Macro Choices](/docs/Choices/MacroChoice/) |
| Group choices into a nested menu | Multi choice | [Multi Choices](/docs/Choices/MultiChoice/) |
| Share configured workflows across vaults | Package | [Share QuickAdd Packages](/docs/Choices/Packages/) |
Most workflows start with either a Template choice or a Capture choice. Add a Macro choice when you need scripting, multiple steps, or data from another plugin or API.
## First workflow
[Section titled “First workflow”](#first-workflow)
Let’s build a capture that adds a timestamped line to your daily journal. It takes about a minute.
1. Open **Settings → QuickAdd**. Type a name like `Add to journal`, choose **Capture** in the dropdown, and click **Add Choice**.
2. Click the gear (⚙) next to your new choice to open its settings.
3. Set **Capture To** to `Journal/{{DATE}}.md` - the note today’s entries land in.
4. Turn on **Capture format** and enter `- {{DATE:HH:mm}} {{VALUE}}` - the shape of one entry.
5. Close the settings. Open the command palette (Ctrl/Cmd+P), run **QuickAdd: Run**, pick `Add to journal`, and type your entry.
QuickAdd writes a line like `- 09:42 Standup moved to Wednesday` into today’s journal note, without opening it. Once it works the way you want, give it a hotkey from the ⚡ icon next to the choice or Obsidian’s Hotkeys settings.
The `{{DATE}}` and `{{VALUE}}` above are [format syntax](/docs/FormatSyntax/): placeholders QuickAdd fills in each time you run the choice. There are placeholders for dates, your answers, links, clipboard content, and more. When a prompt asks you for text, the [suggester system](/docs/SuggesterSystem/) lets you type `[[` or `#` to pull in a file, tag, or heading from your vault.
## Common paths
[Section titled “Common paths”](#common-paths)
### I want examples first
[Section titled “I want examples first”](#i-want-examples-first)
Use the [examples overview](/docs/Examples/) to pick a complete workflow by choice type, difficulty, prerequisites, and outcome.
Good first examples:
* [Capture: Add entries to your daily note](/docs/Examples/Capture_ToDailyNote/)
* [Template: Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/)
* [Macro: Book Finder](/docs/Examples/Macro_BookFinder/)
* [Capture: Canvas Capture](/docs/Examples/Capture_CanvasCapture/)
### I want to automate with scripts
[Section titled “I want to automate with scripts”](#i-want-to-automate-with-scripts)
Start with the [scripting overview](/docs/Advanced/ScriptingGuide/), then move to [User Scripts](/docs/UserScripts/) and the [QuickAdd API reference](/docs/QuickAddAPI/) when you need exact method details.
### I want to call QuickAdd from outside Obsidian
[Section titled “I want to call QuickAdd from outside Obsidian”](#i-want-to-call-quickadd-from-outside-obsidian)
Use [Obsidian URI](/docs/Advanced/ObsidianUri/) for URI-triggered workflows, or the [QuickAdd CLI](/docs/Advanced/CLI/) for shell scripts and external automation.
# API Overview
> Reference for the QuickAdd scripting API - where it is available and the method families for input, choices, dates, AI, and fields
This page maps the QuickAdd API: where you can call it from, and which method family handles each kind of task. For exact signatures and edge cases, follow the links into the [full reference](/docs/QuickAddAPI/).
Reach for the API when a workflow needs scripted input, file operations, formatting, model calls, or access from another plugin. If you only need to create a note or append text, start with [Template Choices](/docs/Choices/TemplateChoice/) or [Capture Choices](/docs/Choices/CaptureChoice/) - add the API when the workflow needs logic.
## Where the API is available
[Section titled “Where the API is available”](#where-the-api-is-available)
| Context | Access pattern | Use it for |
| ----------------- | ---------------------------------- | -------------------------------------------------- |
| Macro user script | `params.quickAddApi` | Scripted macro steps |
| Inline script | `this.quickAddApi` | Small transformations inside templates or captures |
| Other plugin | `app.plugins.plugins.quickadd.api` | Calling QuickAdd from plugin code |
| Templater script | `app.plugins.plugins.quickadd.api` | Prompting or running choices from Templater |
## Common tasks
[Section titled “Common tasks”](#common-tasks)
| Task | Method family | Reference |
| -------------------------------------------------- | ------------------------ | ----------------------------------------------------------------------- |
| Ask for text, selections, dates, or grouped inputs | User input methods | [QuickAdd API Reference](/docs/QuickAddAPI/#user-input-methods) |
| Run another choice from a script | Choice execution | [Choice Execution](/docs/QuickAddAPI/#choice-execution) |
| Read selected text or clipboard content | Utility module | [Utility Module](/docs/QuickAddAPI/#utility-module) |
| Format dates | Date module | [Date Module](/docs/QuickAddAPI/#date-module) |
| Call configured AI providers | AI module | [AI Module](/docs/QuickAddAPI/#ai-module) |
| Read field suggestions from the vault | Field suggestions module | [Field Suggestions Module](/docs/QuickAddAPI/#field-suggestions-module) |
## Recommended path
[Section titled “Recommended path”](#recommended-path)
1. Read the [scripting overview](/docs/Advanced/ScriptingGuide/) if you have not written a QuickAdd script before.
2. Copy a working pattern from the [examples overview](/docs/Examples/).
3. Use the [QuickAdd API reference](/docs/QuickAddAPI/) for exact signatures and edge-case behavior.
## Minimal macro script
[Section titled “Minimal macro script”](#minimal-macro-script)
```javascript
module.exports = async ({ quickAddApi }) => {
const title = await quickAddApi.inputPrompt("Book title");
return `# ${title}`;
};
```
The returned value can be used by later macro steps or inserted through format syntax.
# QuickAdd CLI
> Run, list, and check QuickAdd choices from Obsidian's native CLI, pass variables non-interactively, and create notes from templates
QuickAdd hooks into Obsidian’s own command-line interface, so you can run a choice from a terminal, a shell script, or a scheduled job - no link-building or extra plugins. Point the `obsidian` command at a vault and a choice, and it runs.
```bash
obsidian vault=dev quickadd choice="Daily log"
```
QuickAdd registers these CLI handlers automatically on any Obsidian version that supports plugin CLI commands.
## What you need
[Section titled “What you need”](#requirements)
* Obsidian `1.12.2` or newer (the plugin CLI handler API arrived in `1.12.2`).
* QuickAdd enabled in the target vault.
## The commands
[Section titled “The commands”](#commands)
### Run a choice: `quickadd` / `quickadd:run`
[Section titled “Run a choice: quickadd / quickadd:run”](#quickadd--quickaddrun)
Run a QuickAdd choice from the CLI, by name or by id:
```bash
obsidian vault=dev quickadd choice="Daily log"
obsidian vault=dev quickadd:run id="choice-id"
```
### List your choices: `quickadd:list`
[Section titled “List your choices: quickadd:list”](#quickaddlist)
List every QuickAdd choice (including nested choices inside multis):
```bash
obsidian vault=dev quickadd:list
obsidian vault=dev quickadd:list type=Capture
obsidian vault=dev quickadd:list commands
```
### See what a choice still needs: `quickadd:check`
[Section titled “See what a choice still needs: quickadd:check”](#quickaddcheck)
Check which inputs are still missing before a non-interactive run:
```bash
obsidian vault=dev quickadd:check choice="Daily log"
```
### Create a note from a template: `quickadd:run-template`
[Section titled “Create a note from a template: quickadd:run-template”](#quickaddrun-template)
Create a new note from a template file, with no dedicated Template choice required. This is the scriptable form of the **New note from template** command.
```bash
obsidian vault=dev quickadd:run-template \
path="Templates/Meeting.md" \
value-value="2026-06-14 Standup"
```
* `path=` is the template file (vault-relative). A leading slash is allowed and a missing `.md` extension is added, matching how Template choices resolve paths. If no file resolves there, the command returns `{"ok":false}` up front.
* The new note’s name comes from `{{VALUE}}` - pass it as `value-value=...`. A non-interactive run with an empty or missing name returns `missingFlags` instead of creating an unnamed note. The note is created in Obsidian’s “Default location for new notes”.
* The picker (interactive command) only lists templates inside your configured template folder(s); `path=` here is explicit, so any vault file resolves.
* Like `quickadd:run`, name collisions on the target note still prompt (the file-exists choice is not a pre-collected input). Under `quickadd:interactive` that prompt is forwarded to you like any other.
*Introduced in QuickAdd 2.14.0.*
## Pass variables to a choice
[Section titled “Pass variables to a choice”](#passing-variables)
QuickAdd’s CLI accepts variables three ways:
1. `value-=...` (the same form the URI uses)
2. extra `key=value` args
3. `vars=` for structured values
```bash
obsidian vault=dev quickadd \
choice="Daily log" \
value-project="QuickAdd" \
mood="focused"
obsidian vault=dev quickadd \
choice="Daily log" \
vars='{"project":"QuickAdd","sprint":42}'
```
Values are passed through exactly as provided. If a choice should ignore an accidental leading or trailing space for a specific placeholder, use `|trim` in that format string, for example `{{VALUE:project|trim}}`.
### Names the CLI reserves
[Section titled “Names the CLI reserves”](#reserved-flag-names)
The bare `key=value` form (pattern 2) ignores names that a command already uses as flags or selectors: `choice`, `id`, `vars`, `ui`, `verify` (on `quickadd` / `quickadd:run`), `fields` (on `quickadd:check`), and `path` (on `quickadd:run-template`). If a choice has a variable named after one of these (for example `{{VALUE:verify}}`), pass it with the `value-` prefix or via `vars` instead - neither is ever treated as a flag:
```bash
obsidian vault=dev quickadd choice="My choice" value-verify="a value"
obsidian vault=dev quickadd choice="My choice" vars='{"verify":"a value"}'
```
## What happens when inputs are missing
[Section titled “What happens when inputs are missing”](#non-interactive-behavior)
By default, `quickadd` and `quickadd:run` are non-interactive. If QuickAdd finds missing inputs, it returns a JSON payload with `missing` fields and `missingFlags` suggestions instead of opening prompts.
Pass a returned `missingFlags` entry back exactly as shown. Some generated flags fill internal runtime selections, such as a preselected capture target file.
Add `ui` to allow interactive prompts:
```bash
obsidian vault=dev quickadd choice="Daily log" ui
```
In a [scheduled job](/docs/Advanced/TriggerQuickAddFromOutsideObsidian/#run-quickadd-on-a-schedule), only add `ui` when the job runs while you are logged in and able to answer the prompts.
## Knowing whether anything actually landed
[Section titled “Knowing whether anything actually landed”](#verified-and-effect)
`ok:true` means the choice ran without aborting. It does **not** mean your vault changed. Two more keys answer the questions an automation actually asks:
| Key | Question it answers | Values |
| ---------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `verified` | Did QuickAdd confirm what the engine did? | `true` on the outcome path (`verify` on a Template/Capture choice), `false` when it could not look |
| `effect` | What did the run do to the vault? | `created`, `changed`, `unchanged`, `unknown` |
```bash
obsidian vault=dev quickadd:run choice="Inbox" value-value=" " verify=true
# -> {"ok":true,"choice":{…},"file":"Inbox.md","verified":true,"effect":"unchanged","durationMs":6}
```
That run is working exactly as designed: the capture’s payload was empty, so QuickAdd deliberately left `Inbox.md` alone rather than writing a blank line, and said so in a notice. A Template set to **Do nothing** when the file already exists reports the same. If you are counting captures, writing an idempotency marker, or deciding whether to retry, key off `effect`, not `ok`.
`effect` is present on every **success** payload (`ok:true`), and `unknown` is stated rather than omitted - a missing key reads as `false` in both `jq` and JavaScript, which would turn “QuickAdd did not look” into “nothing happened”. A failed or cancelled run carries `error` instead and no `effect`, because there is no outcome to describe. `verified:false` still means only *“not confirmed - go look”*; it never means *“confirmed that nothing changed”*.
The `obsidian://quickadd` [x-callback](/docs/Advanced/TriggerQuickAddFromOutsideObsidian/) success callback carries the same `effect` value.
Available in the next release
`effect` is new. `capabilities` in the `quickadd:interactive` handshake contains `outcome-effect` on a build that has it.
## Answer run-time prompts from outside: `quickadd:interactive`
[Section titled “Answer run-time prompts from outside: quickadd:interactive”](#interactive-runs-quickaddinteractive)
Some choices prompt at *run time* for inputs that can’t be gathered up front - a macro’s `quickAddApi.suggester` over data it just fetched, an `inputPrompt`, `yesNoPrompt`, `checkboxPrompt`, and so on. `quickadd:interactive` runs a choice and **forwards those prompts to you over a local HTTP bridge**, so an external front end (Raycast, a script) can render them and send back answers, instead of the prompts opening in Obsidian.
```bash
obsidian vault=dev quickadd:interactive choice="Import from Readwise"
# -> {"ok":true,"host":"127.0.0.1","port":51789,"sessionId":"…","token":"…","capabilities":["abort","outcome-effect"]}
```
The command returns connection details immediately and runs the choice in the background. Attach to the session and drive it:
* `GET http://127.0.0.1:/poll?session=&token=` - long-polls for the next event: `{"kind":"prompt","requestId":…,"prompt":{…}}`, `{"kind":"done","result":…}`, `{"kind":"error","error":…}`, or a periodic `{"kind":"idle"}` keepalive (just poll again).
* `POST http://127.0.0.1:/reply?session=&token=` with body `{"requestId":…,"value":…}` to answer, or `{"requestId":…,"cancelled":true}` to cancel (which ends the run - except on an `info` panel, see below).
* `POST http://127.0.0.1:/abort?session=&token=` - end the run. Answers `{"ok":true,"interrupted":}`, where `n` is how many pending prompts it rejected; `409` if the run had already finished (benign - poll for the terminal event); `404` for an unknown session or token, or for any method other than `POST`.
Prompts a Template or Capture run opens itself - the “file already exists” chooser, the folder picker, the note-discovery picker, the heading picker, the capture-target picker - are forwarded like any other. (The AI assistant’s tool-confirmation dialog is the one that is not: run such a choice at the desktop, or set tool confirmation to “never”.)
Available in the next release
Forwarding the run’s own pickers is new. Before it, a Template or Capture run opened them in Obsidian and `/abort` could not reach them. ::: They arrive as `suggester` prompts, and because the engine controls the list, a reply that is not one of the offered `value` tokens is refused rather than acted on (unless the prompt sets `allowCustomInput`, as the folder and discovery pickers do so you can create something new).
Prompt `type`s and the `value` you reply with: `suggester`/`input`/`date` → string, `confirm` → boolean, `checkbox` → string array, `info` → acknowledgement, `form` → an object mapping each field’s `id` to its value. Ordinary and date fields use strings (dates use the `@date:ISO` format), while multi-select fields use string arrays. Use the array form for multi-selects so values containing commas remain unambiguous. The run’s outcome arrives as the `done`/`error` poll event: `done` carries the same `verified` and `effect` keys described under [Knowing whether anything actually landed](#verified-and-effect).
### Cancelling, and ending a run
[Section titled “Cancelling, and ending a run”](#cancelling-and-ending-a-run)
`{"cancelled":true}` is how you say *the user dismissed this prompt*. It ends the run exactly as pressing Escape on the in-app dialog does.
`info` is the exception, because the in-app dialog is: `GenericInfoDialog` resolves on every close path and has no way to abort anything, so the same choice run in Obsidian continues past the panel. Escape is the only gesture an info panel affords, so cancelling one just closes it and the run carries on - matching the app.
To end a run deliberately, `POST /abort`. It rejects whatever the run is blocked on and makes its next prompt fail too, so the run unwinds and delivers its **real** outcome - usually `{"kind":"error","error":"Input cancelled by user"}`, but `done` if it had nothing left to interrupt and simply finished. `/abort` never fabricates a terminal event; keep polling until one arrives. The `interrupted` count tells you whether it stopped anything.
What `/abort` cannot reach
`/abort` interrupts prompts that were routed **to you**. A run that is mid-work between prompts keeps going, so `"interrupted":0` means nothing was waiting on you and the run may still finish and commit its side effects. Keep polling for the terminal event either way.
Available in the next release
`POST /abort` and the `info` behaviour above are new; `"capabilities":["abort"]` in the handshake tells you a build has them. Before them, cancelling an `info` prompt ended the run, and there was no explicit way to end one other than to stop polling and wait out the \~75s disconnect watchdog.
### When a reply is rejected
[Section titled “When a reply is rejected”](#when-a-reply-is-rejected)
Available in the next release
The `400`-and-retry semantics below are new. Before them, a `cancelled` flag that was not the literal `true` was consumed as a cancellation, and a `confirm` prompt with no value was read as “No”.
`/reply` answers `400` and leaves the prompt **pending** when it cannot honour what you sent, so you can correct the reply and POST again. Two cases:
* `cancelled` is present but is not a boolean (`"true"`, `1`, `"no"`). QuickAdd will neither abort on a flag it does not recognise nor quietly answer the prompt on the user’s behalf, so it asks you to fix the flag. Use the literal `true`; `false` and omitting it both mean “this is a real answer”.
* a `confirm` reply whose `value` is not `true`/`false`. The user never answered, and QuickAdd will not invent a “No” for them.
Every other prompt type accepts whatever you send, including an empty answer: `""` and `[]` are things a user genuinely submits in the app (the Skip buttons, optional fields), so they must stay legal here too.
A `409` from `/reply` means nothing was waiting on that `requestId`.
Good to know:
* **Desktop only.** The bridge binds to `127.0.0.1`, is gated by the per-session `token`, rejects browser (`Origin`/`Referer`) and non-loopback `Host` requests, and the server is ephemeral - it starts on the first session and stops when the last one ends.
* **Concurrency.** Each run gets its own `sessionId` + `token`; many can run at once without interfering.
* If no client attaches within \~30s the run is aborted so a prompt can’t hang forever.
*Introduced in QuickAdd 2.16.0.*
# Open QuickAdd from a URI
> Trigger QuickAdd choices with the obsidian://quickadd URI, pass named values, and get the created note's path back via x-callback-url
A special link, `obsidian://quickadd`, runs a QuickAdd choice from outside Obsidian - from an Apple Shortcut, a launcher, another app, or a Markdown link in a note. You give it the name of the choice to run and, optionally, the values to fill in, and QuickAdd runs it just as if you had triggered it yourself.
This is the shape of the link:
```plaintext
obsidian://quickadd?choice=[&value-VALUE_NAME=...]
```
Encode everything
Every parameter name and value has to be [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) to work. An online tool like [urlencoder.org](https://www.urlencoder.org/) makes it easy to encode parts of the link.
The only required part is `choice`, which picks the choice to run **by its name**. The name has to match exactly, or QuickAdd cannot find it.
If you would rather script this from a shell than build links, see [QuickAdd CLI](/docs/Advanced/CLI/) for the native Obsidian CLI commands.
## Pass values into the choice
[Section titled “Pass values into the choice”](#pass-values-into-the-choice)
Add a `value-` parameter for each [named value](/docs/FormatSyntax/) the choice asks for. A capture asking for `{{VALUE:contents}}` is filled by `value-contents=...`.
If a variable name has a space in it, encode the space as `%20` like everything else. A variable named `log notes` is passed as `value-log%20notes=...`.
Values are used exactly as they are encoded in the link. If a format should ignore an accidental leading or trailing space for one placeholder, add `|trim` to it, for example `{{VALUE:log notes|trim}}`.
Unnamed values - a bare `{{VALUE}}`/`{{NAME}}`, or `{{MVALUE}}` - cannot be filled from the link. QuickAdd prompts for them inside Obsidian as usual.
## Choose which vault: `vault=`
[Section titled “Choose which vault: vault=”](#vault-parameter)
Like every Obsidian URI, you can add a `vault` parameter to say which vault to run QuickAdd in. Leave it out and Obsidian uses your most recent vault.
```plaintext
obsidian://quickadd?vault=My%20Vault&choice=Daily%20log&value-contents=Lorem%20ipsum.
```
## Get a result back: x-callback-url
[Section titled “Get a result back: x-callback-url”](#getting-a-result-back-x-callback-url)
QuickAdd can open a callback link once a choice finishes, so whatever triggered it (an Apple Shortcut, say) can react to the result and receive the path of the affected note. This follows the [x-callback-url](http://x-callback-url.com/) convention.
Off by default
This is opt-in. Turn on **Settings → AI & online → Allow URI x-callback-url** first. It is off by default because the callback link is controlled by whoever creates the `obsidian://` link, and the callback can carry your note’s vault path.
Template and Capture only
Callbacks work for **Template** and **Capture** choices. Triggering a Macro or Multi choice with a callback fires `x-error` with `errorCode=unsupported-choice-type` instead. (You can still trigger Macro and Multi choices from the URI without a callback.)
### Which callback fires when
[Section titled “Which callback fires when”](#callback-parameters)
| Parameter | Fired when |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `x-success` | the choice completed successfully |
| `x-error` | the choice failed, was aborted, was not found, or is unsupported |
| `x-cancel` | you cancelled a prompt while the choice was running |
| `x-callback-url` | legacy shorthand - used only when none of the above are present; it then fires for **success and cancel** (never error) |
If you do not provide a slot, nothing opens for that outcome (there is no fallback - a cancel with no `x-cancel` opens nothing).
Only `shortcuts:` and `obsidian:` callback links are allowed. Any other scheme (such as `https:`, `file:`, or `javascript:`) is rejected and the choice does not run.
### What QuickAdd sends back
[Section titled “What QuickAdd sends back”](#result-parameters)
QuickAdd appends these query parameters to your callback link:
* On `x-success`: `status=success`, and - for Template/Capture - `path=` and `url=` pointing at the affected note.
* On `x-error`: `status=error` and a stable `errorCode` (one of `choice-not-found`, `unsupported-choice-type`, `execution-failed`, `execution-aborted`, `bad-callback-url`). The detailed error message stays in Obsidian’s log and is never sent to the callback.
* On `x-cancel`: `status=cancel`.
### Encode your callback link twice
[Section titled “Encode your callback link twice”](#encoding-your-callback-url-important)
Your callback link is itself a value inside the `obsidian://` link, so it **must be fully percent-encoded** (double-encoded). If you leave a `=` or `&` un-encoded, Obsidian’s URI parser silently cuts the callback off before QuickAdd ever sees it.
For example, this looks reasonable but is **broken** - the `=My%20Cool%20Shortcut` part is dropped, leaving `shortcuts://run-shortcut?name`:
```text
obsidian://quickadd?choice=Daily%20log&x-success=shortcuts://run-shortcut?name=My%20Cool%20Shortcut
```
The **correct** form encodes the entire `x-success` value:
```text
obsidian://quickadd?choice=Daily%20log&x-success=shortcuts%3A%2F%2Frun-shortcut%3Fname%3DMy%2520Cool%2520Shortcut
```
(Note the `%2520` - the spaces inside the shortcut name are encoded twice because the value is decoded once by Obsidian and once by Shortcuts.)
### Full example
[Section titled “Full example”](#example)
Trigger a capture, run a shortcut on success, and pass along the created note’s path:
```text
obsidian://quickadd?vault=My%20Vault&choice=Daily%20log&value-contents=Lorem%20ipsum&x-success=shortcuts%3A%2F%2Frun-shortcut%3Fname%3DLog%2520Saved
```
On success QuickAdd opens your `x-success` link with these extra query parameters appended (shown here decoded - they are percent-encoded on the wire):
* `status` = `success`
* `path` = `Daily/2026-06-14.md`
* `url` = `obsidian://open?vault=My Vault&file=Daily/2026-06-14.md`
Your shortcut reads them from the link it was opened with.
Mobile
QuickAdd opens callbacks with `window.open`, exactly like Obsidian’s own x-callback support. Whether a custom scheme such as `shortcuts:` launches reliably on iOS is a platform behaviour shared with Obsidian core - verify on your device.
*Introduced in QuickAdd 2.14.0.*
## Watch out for sync services
[Section titled “Watch out for sync services”](#important-sync-service-limitations)
Caution
When you use QuickAdd via URI with a sync service (Obsidian Sync, iCloud, Dropbox, and so on), there is a limitation to be aware of.
**If Obsidian hasn’t been opened on a device**, files created on other devices haven’t synced to it yet. QuickAdd can then create a duplicate file that overwrites the synced version when it finally arrives.
### How this goes wrong
[Section titled “How this goes wrong”](#example-scenario)
1. You create a Daily Note on your laptop.
2. Without opening Obsidian on your phone, you trigger a Capture via URI.
3. QuickAdd checks whether the Daily Note exists (it doesn’t, locally).
4. QuickAdd creates a new Daily Note.
5. When sync runs, the new file overwrites the one from your laptop.
### How to avoid it
[Section titled “How to avoid it”](#workarounds)
* **Open Obsidian first**: always open Obsidian and wait for sync before using URIs.
* **Use device-specific names**: configure different filename formats per device (for example `{{DATE}}-mobile`).
* **Capture to active file**: use an already-open note to avoid creating a file at all.
* **Include timestamps**: add `{{TIME}}` to filenames so each one is unique.
This is a fundamental limitation of file-based sync services and cannot be fully resolved without sync-status APIs.
# One-page Inputs
> Collect every input a choice needs in a single form, filled once, instead of one prompt at a time
Normally QuickAdd asks for inputs one prompt at a time. Turn on one-page inputs and it gathers everything a choice needs into a single form you fill once, then runs. This is nicer when a choice asks for several things at once - a title, a date, and a status, say - and you would rather see them all together than click through them one by one.
For a task-oriented overview of prompts in general, see [Controlling Prompts](/docs/ControllingPrompts/).
## Turn it on
[Section titled “Turn it on”](#enable)
Go to **Settings → QuickAdd** and toggle **One-page input for choices**.
It works with Template, Capture, and Macro choices. For Macros, only the inputs a script declares are collected (see [User scripts](#user-scripts-declare-inputs-optional) below).
## Turn it on or off for one choice
[Section titled “Turn it on or off for one choice”](#per-choice-override)
Template and Capture choice builders have a **One-page input override** dropdown that overrides the global setting for that one choice:
* **Follow global setting** - use whatever the global toggle is set to (default).
* **Always** - force the one-page form for this choice even when it is off globally.
* **Never** - use step-by-step prompts for this choice even when it is on globally.
## What ends up in the form
[Section titled “What ends up in the form”](#what-gets-collected)
QuickAdd scans the choice for placeholders and turns each one into a field:
* Placeholders in file names, templates, and capture content: `{{VALUE}}`, `{{VALUE:name}}`, `{{VDATE:name, YYYY-MM-DD}}`, `{{FIELD:name|...}}`, and `{{FILE:folder|...}}`.
* Nested `{{TEMPLATE:path}}` includes are scanned recursively, so their prompts show up too.
* `{{VALUE|type:multiline}}` and `{{VALUE:name|type:multiline}}` become textareas.
* `{{VALUE:name|type:number|min:1|max:10}}` becomes a bounded numeric input, and `{{VALUE:name|type:slider|min:0|max:100|step:5}}` becomes a slider plus numeric input.
* The capture target file, when you are capturing to a folder or a tag.
* Inputs declared by a user script inside a macro, if the script provides them.
### How dates behave in the form
[Section titled “How dates behave in the form”](#date-ux)
* Date fields accept natural language, like `today` or `next friday`.
* Short aliases work and are configurable in settings: `t` (today), `tm` (tomorrow), `yd` (yesterday).
* The field shows a formatted preview and stores a normalized `@date:ISO` value internally.
### How FIELD inputs behave
[Section titled “How FIELD inputs behave”](#field-ux)
* `{{FIELD:...}}` inputs suggest values from your vault (using Dataview when it is available, with a manual fallback otherwise).
* `{{FIELD:...|multi}}` is not shown inline in the form, because vault field values can contain commas. QuickAdd collects the rest of the form first, then opens the regular multi-select for that field.
### How FILE inputs behave
[Section titled “How FILE inputs behave”](#file-ux)
Available in the next release
The inline searchable FILE picker described below is on `master` and will ship in the next QuickAdd release.
* `{{FILE:folder}}` appears as a searchable picker in the form. Search matches the friendly note title, file name, and full vault path.
* The selected file is shown above the search field and can be removed or replaced. Single-select fields keep the same first-file default as the previous dropdown.
* `{{FILE:folder|multi}}` stays in the same form. Pick several files without opening a second modal, and remove the last pick by pressing Backspace in an empty search field.
* Multi-select results keep the folder’s file order. File names and friendly labels containing commas are handled as complete values.
## Fields you can leave empty
[Section titled “Fields you can leave empty”](#optional-fields)
A field marked with the [`|optional` flag](/docs/FormatSyntax/#optional-fields) shows an **(optional)** badge and may be left blank. Leaving it blank stores an intentional empty value, so the step-by-step prompt will not ask for it again later.
Good to know:
* A field counts as optional only when **every** occurrence of that variable across the scanned formats is flagged.
* Optional dropdowns get a **Skip (leave empty)** entry; the first real option stays preselected.
* An optional date field left blank resolves to empty. If what you typed cannot be read as a date, the field is handed to the regular step-by-step date prompt after you submit, instead of silently becoming empty.
## When the form is skipped
[Section titled “When the form is skipped”](#skipping-the-modal)
The form only opens when it has something to ask:
* If every required input already has a value (for example, prefilled by an earlier macro step), the form does not open.
* An empty string counts as an intentional value and will not prompt again. This applies to `{{VDATE}}` too: a script-set `""` renders empty instead of re-prompting.
* For Capture choices, a non-empty editor selection prefills `{{VALUE}}` during preflight when selection-as-value is enabled.
Required date fields
A **required** date field with a default applies the default automatically when you leave it blank. A **required** date field left blank with no usable default is re-asked by the step-by-step date prompt after you submit. Optional date fields left blank stay empty.
### What Cancel does
[Section titled “What Cancel does”](#cancel-behavior)
* Cancelling the form (Cancel button or Esc) cancels the whole run. QuickAdd does not fall back to the step-by-step prompts.
* If the form fails to open for some other reason (for example, a requirement could not be collected), QuickAdd logs a warning and runs the choice with the standard step-by-step prompts instead.
### Reserved internal variables
[Section titled “Reserved internal variables”](#internals-and-reserved-variables)
QuickAdd uses reserved variable ids prefixed with `__qa.` for internal wiring during preflight and runtime. For example, `__qa.captureTargetFilePath` stores the capture target chosen in the form so the capture engine can skip its own file picker.
These internal keys will not collide with your own variables. Avoid using the `__qa.` prefix in your scripts.
***
## User scripts: declare inputs (optional)
[Section titled “User scripts: declare inputs (optional)”](#user-scripts-declare-inputs-optional)
To have a user script’s inputs appear in the one-page form during preflight, export a static `quickadd.inputs` spec alongside your default export. This is optional and non-executing.
Example (function default export):
```js
export default async function entry(params, settings) {
// ... your script ...
}
export const quickadd = {
inputs: [
{ id: "project", label: "Project", type: "text", defaultValue: "Inbox" },
{ id: "due", label: "Due date", 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"] }
]
};
```
Example (object default export):
```js
export default {
async entry(params, settings) {
// ... your script ...
}
};
export const quickadd = {
inputs: [ { id: "topic", type: "text" } ]
};
```
Supported input fields:
* `id` (string, required)
* `label` (string)
* `type` (“text” | “number” | “textarea” | “dropdown” | “date” | “field-suggest” | “suggester” | “slider”)
* `placeholder` (string)
* `defaultValue` (string)
* `options` (string\[] for dropdown and suggester)
* `numericConfig` (object for number: `{ min?: number, max?: number, step?: number }`)
* `sliderConfig` (object for slider: `{ min: number, max: number, step?: number }`; `min` and `max` are required, `step` defaults to `1`)
* `dateFormat` (string for date)
* `description` (string)
* `optional` (boolean - field may be left empty; shows an “(optional)” badge)
* `suggesterConfig` (object for suggester: `{ allowCustomInput?: boolean, caseSensitive?: boolean, multiSelect?: boolean }`)
Field type details:
* `text`: single-line text input
* `number`: numeric input, optionally bounded by `numericConfig`
* `textarea`: multi-line text input
* `dropdown`: fixed dropdown menu (no search, must select from list)
* `date`: date input with natural language support
* `field-suggest`: vault field suggestions (uses `{{FIELD:...}}` syntax)
* `slider`: bounded numeric input with a slider and editable number field. Requires `sliderConfig.min` and `sliderConfig.max`; invalid configs fall back to `number`.
* `suggester`: searchable autocomplete with custom options (allows typing custom values)
* Supports multi-select mode via `suggesterConfig.multiSelect: true`
* Multi-select: select multiple items, separated by commas. Suggestions stay open after each selection.
## Scripts: request inputs at runtime (API)
[Section titled “Scripts: request inputs at runtime (API)”](#scripts-request-inputs-at-runtime-api)
From within a script, you can open one form that collects several inputs at once using the QuickAdd API.
```js
export default async function entry({ quickAddApi }) {
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"],
placeholder: "Type to search tags..."
},
]);
// Access collected values
const { project, due, status, tags } = values;
}
```
Example with dynamic options (from Dataview):
```js
export default async function entry({ quickAddApi, app }) {
// Get dynamic options from Dataview
const 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..."
},
]);
const { project } = values;
}
```
Example with multi-select:
```js
export default async function entry({ quickAddApi }) {
const 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"
const { tags } = values;
// Split into array if needed
const tagArray = tags.split(', ').filter(Boolean);
}
```
Behavior:
* Values already present in variables are used and not re-asked.
* Only missing inputs are prompted in the form.
* Returned values are also stored into `variables` for later steps in the macro.
***
## Good to know
[Section titled “Good to know”](#notes)
* Macro support is best-effort: user scripts can declare inputs via `quickadd.inputs`.
* Preflight may import user script modules to statically read `quickadd.inputs`. This can execute module top-level code.
* Inline scripts aren’t scanned for input declarations yet.
* You can still prompt ad-hoc (for example with `inputPrompt` or a suggester); those values are treated as prefilled and skip future one-page prompts.
* Closing the `requestInputs` form without submitting rejects with `MacroAbortError("Input cancelled by user")`, which stops the macro unless you catch it.
# Scripting Overview
> Choose between user scripts, inline scripts, and macros, and see how values move through a macro run via the shared variables object
QuickAdd scripts are JavaScript files that run inside Obsidian. They can ask you for input, call Obsidian’s own APIs, read data from other plugins, and hand values back to a Macro. This page helps you pick which kind of script to write, then points you at the detailed guides.
You don’t need to be a programmer to use scripting, but you do need to be comfortable pasting and lightly editing JavaScript.
## Which one do I want?
[Section titled “Which one do I want?”](#which-scripting-feature-should-i-use)
| Need | Use | Why |
| --------------------------------------------------- | -------------------- | -------------------------------------------------------- |
| A reusable script with settings | User script | Best for larger workflows and shared code |
| A small transformation inside a template or capture | Inline script | Keeps tiny logic close to the format using it |
| Several script and choice steps in sequence | Macro choice | Coordinates order, variables, and abort behavior |
| A script users can configure from the QuickAdd UI | Script with settings | Lets non-coders change values without editing JavaScript |
## What a user script looks like
[Section titled “What a user script looks like”](#basic-user-script-shape)
Every user script exports one function. QuickAdd calls it with a `params` object and waits for it to finish:
```javascript
module.exports = async (params) => {
const { app, quickAddApi, variables } = params;
const title = await quickAddApi.inputPrompt("Title");
variables.title = title;
return title;
};
```
The `params` object is how your script reaches everything it needs:
* `app`: the Obsidian app instance
* `quickAddApi`: QuickAdd’s prompt, utility, AI, and execution helpers
* `variables`: values shared across macro steps
## How a value travels through a macro
[Section titled “How a value travels through a macro”](#how-values-move-through-a-macro)
When a macro runs several steps, they pass values to each other through the shared `variables` object:
1. A choice or script asks for a value.
2. QuickAdd stores that value in `variables`.
3. Later template, capture, and script steps can reuse it.
4. If a prompt is cancelled, QuickAdd aborts the macro unless your script handles the cancellation.
Use named values like `{{VALUE:project}}` when several macro steps should share one prompt.
## A good order to learn this in
[Section titled “A good order to learn this in”](#suggested-learning-order)
1. [Macro Choices](/docs/Choices/MacroChoice/) for how macro steps are assembled.
2. [User Scripts](/docs/UserScripts/) for complete scripting patterns.
3. [Scripts with Settings](/docs/Advanced/scriptsWithSettings/) for configurable scripts.
4. [QuickAdd API Reference](/docs/QuickAddAPI/) for exact method details.
## Debugging
[Section titled “Debugging”](#debugging)
Sprinkle `console.log` through a script while you build it, then read the output in Obsidian’s developer console. Keep each script small enough that you can test one step at a time before wiring it into a longer macro.
# Scripts with user settings
> Give a user script configurable fields - text, secret, checkbox, dropdown, and format - so anyone can set it up from the QuickAdd UI without touching the code
A script with settings puts configurable fields right in QuickAdd’s UI, so anyone can set it up - an API key, a folder path, an on/off toggle - without editing the JavaScript. You write the script once and expose the parts that should change; everyone else fills in a form.
Any script with settings gets a gear (⚙️) button next to its name in a macro. Click it to open that script’s settings menu. For a real-world example, see the [Movies](/docs/Examples/Macro_MovieAndSeriesScript/) macro.
## Add settings to a script
[Section titled “Add settings to a script”](#creating-a-script-with-settings)
Instead of exporting a plain function, export an **object** with two properties: `entry` (the function that runs) and `settings` (what to show in the UI).
```js
const TEXT_FIELD = "Text field";
module.exports = {
entry: async (QuickAdd, settings) => {
// Logic here
const textFieldSettingValue = settings[TEXT_FIELD];
},
settings: {
name: "Demo",
author: "Christian B. B. Houmann",
options: {
[TEXT_FIELD]: {
type: "text",
defaultValue: "",
placeholder: "Placeholder",
description: "Description here.",
},
"API Key": {
type: "secret",
id: "api-key",
placeholder: "Paste API key",
description: "Stored securely with Obsidian SecretStorage.",
},
"Checkbox": {
type: "checkbox",
defaultValue: false,
},
"Dropdown": {
type: "dropdown",
defaultValue: "Option 1",
options: [
"Option 1",
"Option 2",
"Option 3",
],
},
"Format": {
type: "format",
defaultValue: "{{DATE:YYYY-MM-DD}}",
placeholder: "Placeholder",
},
}
},
};
```
This script’s settings menu shows a text field, a secret API-key field, a checkbox, a dropdown, and a format field - one per entry in `options`:

How the pieces fit together:
* **`entry`** runs when the script executes. It receives two arguments: the `QuickAdd` object (the same thing passed to any [script in a macro](/docs/Choices/MacroChoice/)) and `settings`, an object holding the values the user set. The argument names are up to you.
* **`settings.name`** and **`settings.author`** are shown in the settings menu.
* **`settings.options`** defines the fields. Each key is the setting’s name (and how you read its value back, like `settings["Text field"]`); each value is an object describing the field. Add a `description` to any field to show help text beneath it.
## The field types
[Section titled “The field types”](#setting-types)
Set each field’s `type` to one of these:
* `text` and `input`: A text field.
* `secret`: A password-style input stored with Obsidian SecretStorage. QuickAdd stores only a reference in `data.json`; package exports omit secret values and local secret references. Add an optional `id` to give the stored secret a stable key if the visible setting label changes later. The older `text` / `input` plus `secret: true` form is still treated as a secret setting.
* `textarea`: A multi-line text area.
* `checkbox` and `toggle`: A checkbox.
* `dropdown` and `select`: A dropdown.
* `format`: A format field, adhering to [format syntax](/docs/FormatSyntax/).
# Trigger QuickAdd from outside Obsidian
> Launch QuickAdd choices from launchers, shortcuts, and schedulers using the obsidian://quickadd URI or the Obsidian CLI, no extra plugins
You can run a QuickAdd choice from a launcher, a script, a scheduled job, or a link in a note - without opening Obsidian and clicking around, and without any extra plugins. There are two built-in ways in:
* The `obsidian://quickadd` link, for anything that can open a URL.
* The Obsidian CLI, for anything that runs a shell command, like a scheduled job.
You do not need the Advanced URI plugin for this. QuickAdd has its own handler.
## Get the choice ready
[Section titled “Get the choice ready”](#prepare-the-choice)
Before you automate a choice:
1. Give it a unique name. Triggers select by choice name, and if two choices share a name, QuickAdd runs the first match it finds.
2. Use [named values](/docs/FormatSyntax/#named-value) for anything you want to pass in from outside, for example `{{VALUE:entry}}` or `{{VALUE:project}}`.
3. Keep prompts out of scheduled jobs where you can. A scheduled job works best when every required value is passed up front.
You can list your choices from the CLI to confirm names and ids:
```bash
obsidian vault="My Vault" quickadd:list
```
## Build the link
[Section titled “Build the link”](#native-uri-syntax)
The shortcut and in-note recipes below all open the same link shape - the `vault` to run in, the `choice` to run, plus a `value-` parameter for each named value you want to pass (everything URL-encoded). Scheduled jobs use the [CLI](#run-quickadd-on-a-schedule) instead of a link.
```text
obsidian://quickadd?vault=My%20Vault&choice=Daily%20log&value-entry=Finished%20review
```
The full link reference - the parameter breakdown, passing values, `|trim`, which placeholders can’t be filled from a link, and the opt-in callback links like `x-success` - is on [Open QuickAdd from a URI](/docs/Advanced/ObsidianUri/).
## Trigger from a desktop shortcut
[Section titled “Trigger from a desktop shortcut”](#desktop-shortcuts)
Any desktop shortcut or launcher that can open a URL can open `obsidian://quickadd`.
### macOS
[Section titled “macOS”](#macos)
Use Shortcuts with an **Open URLs** action, or run:
```bash
/usr/bin/open 'obsidian://quickadd?vault=My%20Vault&choice=Daily%20log'
```
If you use a shell command, quote the whole link. The `&` character has special meaning in shells unless it is quoted.
### Windows
[Section titled “Windows”](#windows)
For a desktop shortcut target or launcher command, use:
```bat
cmd.exe /c start "" "obsidian://quickadd?vault=My%20Vault&choice=Daily%20log"
```
The empty `""` is intentional. In `cmd.exe start`, the first quoted string is the window title, not the command to run.
### Linux
[Section titled “Linux”](#linux)
Use `xdg-open` from a desktop session:
```bash
xdg-open 'obsidian://quickadd?vault=My%20Vault&choice=Daily%20log'
```
For a `.desktop` launcher, put the command in a small shell script and point `Exec` at that script. This avoids desktop-file escaping problems with the percent signs in URL-encoded values:
```sh
#!/usr/bin/env sh
xdg-open 'obsidian://quickadd?vault=My%20Vault&choice=Daily%20log'
```
```ini
[Desktop Entry]
Type=Application
Name=Daily log
Exec=/home/alice/bin/quickadd-daily-log
Terminal=false
```
## Run QuickAdd on a schedule
[Section titled “Run QuickAdd on a schedule”](#run-quickadd-on-a-schedule)
QuickAdd has no built-in background scheduler. Use your operating system’s scheduler to run Obsidian’s native CLI command:
```bash
obsidian vault="My Vault" quickadd:run choice="Daily log" value-entry="Scheduled check"
```
`quickadd:run` is non-interactive by default - a choice that still needs input returns JSON with `missingFlags` instead of opening prompts, and `quickadd:check` tells you up front what to pass. Those semantics, the `ui` flag for runs that may prompt, and the rest of the commands are covered in the [QuickAdd CLI reference](/docs/Advanced/CLI/); this section is about wiring the command into each platform’s scheduler.
Caution
Use full paths in schedulers. They usually do not load the same `PATH` as your terminal.
### macOS launchd
[Section titled “macOS launchd”](#macos-launchd)
Use the full path from `command -v obsidian`. In a launchd plist, pass each argument as its own string:
```xml
ProgramArguments/opt/homebrew/bin/obsidianvault=My Vaultquickadd:runchoice=Daily logvalue-entry=Scheduled check
```
Depending on your install, the Obsidian CLI path may be `/usr/local/bin/obsidian` instead.
### Windows Task Scheduler
[Section titled “Windows Task Scheduler”](#windows-task-scheduler)
Use `Obsidian.com`, not `Obsidian.exe`, for CLI commands:
```text
Program/script:
C:\Users\alice\AppData\Local\Obsidian\Obsidian.com
Arguments:
vault="My Vault" quickadd:run choice="Daily log" value-entry="Scheduled check"
```
For a scheduled URI action instead, run:
```text
Program/script:
cmd.exe
Arguments:
/c start "" "obsidian://quickadd?vault=My%20Vault&choice=Daily%20log"
```
URI actions need a logged-in desktop session. In Task Scheduler, use “Run only when user is logged on” for URL-opening tasks.
### Linux cron or systemd user timers
[Section titled “Linux cron or systemd user timers”](#linux-cron-or-systemd-user-timers)
Use the full CLI path:
```cron
0 9 * * * /home/alice/.local/bin/obsidian vault="My Vault" quickadd:run choice="Daily log" value-entry="Scheduled check"
```
Run GUI-related jobs only from your user desktop session. If a cron job cannot reach your desktop session, prefer a systemd user timer or run the command from your desktop environment’s scheduler.
## Add links and buttons to a note
[Section titled “Add links and buttons to a note”](#in-note-links-and-buttons)
Plain Markdown links work well for dashboard notes:
```markdown
[New idea](obsidian://quickadd?vault=My%20Vault&choice=New%20idea)
[Log work](obsidian://quickadd?vault=My%20Vault&choice=Work%20log&value-project=QuickAdd)
```
Clicking the link runs the choice.
If you use a button plugin for styling, point it at the same `obsidian://quickadd` link. Another QuickAdd-native option is to enable the command toggle on the choice, then configure the button to run the generated Obsidian command for that choice.
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
* **Nothing happens**: make sure QuickAdd is enabled in the selected vault and that the choice name is encoded and spelled exactly.
* **The wrong choice runs**: rename choices so the externally triggered choice name is unique.
* **A scheduled run returns JSON instead of capturing**: the choice still needs input. Copy each entry from the returned `missingFlags` into your command and replace `` with the value you want to pass.
* **The command works in a terminal but not from a scheduler**: use the full path to the Obsidian CLI, and make sure the job runs in your user desktop session.
* **An older thread suggests `obsidian://advanced-uri?...`**: replace it with `obsidian://quickadd?...`. QuickAdd has its own URI handler.
# AI Assistant Reference
> Configure AI providers, send prompt templates from macros or scripts, and use structured output, tool calling, and token budgets
QuickAdd’s AI Assistant sends a prompt to an AI model and drops the reply back into your workflow. There are two ways to use it:
* **From a Macro command** - no code. You pick a prompt template, and QuickAdd stores the model’s reply as a variable that later steps can insert.
* **From a User Script** - for structured output, tool calling, or custom control flow.
By the end you have an AI step wired into a choice: generate a note title, summarize a selection, or answer a question from your vault.
Note
The AI settings button and AI requests are available only when **Disable AI & online features** is turned off in QuickAdd settings.
## Setup
[Section titled “Setup”](#setup)
1. Create a folder for AI prompt templates, for example `bins/ai_prompts`.
2. Open QuickAdd settings.
3. In the choice list, click the **Configure AI Assistant** icon button. It uses the sparkles icon at the bottom of the list.
4. Set **Prompt template folder path** to the folder you created.
5. Click **Edit providers** and configure at least one provider and model.
6. Choose a **Default model**, or leave it as **Ask me** to pick a model each run.

Prompt templates are Markdown notes in your prompt template folder. They can use QuickAdd [Format Syntax](/docs/FormatSyntax/), including values collected earlier in the same macro.
Prompt templates can run code
A prompt template goes through the **full** QuickAdd format pass every time the AI command fires - including [inline scripts](/docs/InlineScripts/) (`js quickadd` code fences), `{{MACRO:}}` and `{{TEMPLATE:}}`. That is the same trust model as a Template choice: a prompt template is not just text sent to the model, it is a note QuickAdd executes. Review prompt templates you did not write yourself - for example ones that arrive with an imported [package](/docs/Choices/Packages/) - exactly as you would review a script.
After setup, add an **AI Assistant** command to a Macro. The command formats the selected prompt template, sends it to the selected model, then stores the response as macro variables for later steps.

## What each setting does
[Section titled “What each setting does”](#settings-semantics)
Some of these settings are read live on every run; two of them are only a template for new commands. The difference matters, so it is called out per setting:
* **Prompt template folder path** is the folder QuickAdd reads prompt-template notes from. Read live on every run.
* **Providers** is the list of model endpoints and model ids QuickAdd can use. Read live on every run.
* **Default model** and **Default system prompt** are the starting values for **new** AI Assistant Macro commands: they are copied into a command when you add it. Editing a default later does not change commands you already created - edit each command instead. Setting the default model to **Ask me** makes new commands open a model picker at run time.
* **Show assistant** controls QuickAdd’s AI progress notices. Read live on every run.
* **Confirm AI tool calls** controls script-agent tool confirmation. Read live on every run. See [Tool approval and safety](#tool-approval-and-safety).
The script APIs behave differently from Macro commands here: `quickAddApi.ai.prompt()`, `chunkedPrompt()`, and `ai.agent()` read the **live** default system prompt at call time when you pass no `systemPrompt`/`system` override (the model is always passed explicitly).
Each AI Assistant Macro command holds its own:
* **Prompt template**, which is a Markdown note in the prompt template folder, not raw prompt text.
* **Model**, the model this command uses. Copied from the default at creation; **Ask me** opens a model picker at run time.
* **Output variable name**, which controls the variable names written for later Macro steps.
* **System prompt**, sent with this command’s requests. Copied from the default at creation.
* Advanced model parameters, described in [Advanced sampling settings](#advanced-sampling-settings).
### System prompts are sent as written
[Section titled “System prompts are sent as written”](#system-prompt-is-literal)
QuickAdd resolves [Format Syntax](/docs/FormatSyntax/) in the **prompt template** only. The system prompt - both the default and a command’s own - goes to the model exactly as you typed it, so `{{DATE}}` in a system prompt arrives at the model as the eight characters `{{DATE}}`.
Put anything that needs a token in the prompt template instead.
## Connect a provider
[Section titled “Connect a provider”](#providers-and-local-models)
QuickAdd supports OpenAI-compatible providers, Google Gemini, and Anthropic. Custom or unknown providers use the OpenAI-compatible request shape by default.
Built-in provider cards are available for:
* OpenAI
* Gemini
* Anthropic
* Groq
* TogetherAI
* OpenRouter
* Hugging Face
* Mistral
* DeepSeek
Note
Provider API keys are stored through Obsidian SecretStorage. QuickAdd stores the secret reference in settings, not the key value. Older plaintext provider keys are migrated to SecretStorage.
### Add a provider
[Section titled “Add a provider”](#add-a-provider)
1. Open **AI Assistant settings**.
2. Click **Edit providers**.
3. Click **Add provider**.
4. Pick a provider card, select a SecretStorage entry for the API key, then click **Connect**.
Connecting a provider imports its current model list right away, so you can pick a working model immediately. If the live import fails (for example, while offline), the built-in providers fall back to a shipped model list and refresh automatically once the provider is reachable.
For a provider that is not listed, click **Add custom…** under **Custom provider**. Set the provider name, endpoint, API key secret if needed, model source, and models manually.
### Local models and Ollama
[Section titled “Local models and Ollama”](#local-models-and-ollama)
Use **Custom provider** for Ollama and most local OpenAI-compatible servers.
For Ollama:
```text
Name: Ollama
Endpoint: http://localhost:11434/v1
API key: leave blank
Model source: Provider models endpoint
Models: import from the running Ollama server, or add the model name manually
```
Leaving the API key blank works for Ollama. Model import from `/v1/models` sends no `Authorization` header when the key is blank. Regular OpenAI-compatible chat requests still include an empty `Bearer` header. If your local server rejects that, configure the server to allow it or select a SecretStorage entry with the token it expects.
When adding a model manually, the model name must match the id your server expects, such as `mistral` or `llama3.1`. The **Max tokens** value is the model’s context window. See [Model settings and token budgets](#model-settings-and-token-budgets).
### One name, two providers
[Section titled “One name, two providers”](#provider-ids-and-duplicate-model-names)
Every provider has a stable **ID** - a short slug like `openai` or `my-proxy`, shown in the provider’s edit form. The ID never changes, even if you rename the provider, and scripts use it to address a model on a specific provider.
Two providers can serve models with the same name - for example, the official OpenAI provider and an OpenAI-compatible proxy can both list `gpt-4o`. Model dropdowns group models by provider so you always pick a specific provider’s model, and QuickAdd remembers that choice. Reordering providers, renaming them, or auto-syncing new models never changes which endpoint an existing command talks to.
If the provider a command is pinned to is later deleted, QuickAdd falls back to the first provider that serves a model with that name and warns you about the switch. Re-select the model in the command to pin it again.
*Introduced in QuickAdd 2.19.0.*
### Where QuickAdd gets the model list: Model source
[Section titled “Where QuickAdd gets the model list: Model source”](#model-source)
Each provider has a **Model source** setting:
* **Provider models endpoint** asks the provider for its model list. QuickAdd speaks each provider’s native protocol here: OpenAI-compatible `/v1/models`, Anthropic’s `/v1/models`, and Gemini’s `ListModels`. This is also the usual choice for local providers like Ollama when the server is running.
* **models.dev directory** imports from the public models.dev directory when that directory knows the provider.
* **Automatic** tries the provider first and falls back to models.dev when QuickAdd can map the endpoint.
Imports skip entries that cannot serve chat requests (image generators, text-to-speech voices, embedding models), and they carry each model’s context window, output limit, and sampling support where the source reports them.
If model import fails, you can still add models manually. Use the provider’s exact model id and the model’s context-window token count.
### Keep model lists current: Auto-sync
[Section titled “Keep model lists current: Auto-sync”](#auto-sync)
Each provider has an **Auto-sync models** toggle. While it is on, QuickAdd imports new models and refreshed context limits from the provider’s model source once a day and whenever provider settings open, so model lists stay current without plugin updates. Auto-sync only adds models and updates metadata - it never removes models you have configured. Use **Sync now** to refresh on demand.
Auto-sync is on by default for the built-in OpenAI and Gemini providers and for providers added from a card. It does nothing while **Disable AI & online features** is on.
## Model settings and token budgets
[Section titled “Model settings and token budgets”](#model-settings-and-token-budgets)
### Max tokens is the context window
[Section titled “Max tokens is the context window”](#max-tokens)
In the provider model list, **Max tokens** means the model’s context window. It is the total amount of prompt plus response context the model can handle, according to the configured provider metadata or the value you entered manually.
QuickAdd uses this value for local estimates, model lookup, and chunk sizing. It does not mean “make the answer this long”, and setting it higher than the provider actually supports does not increase the provider’s real limit.
QuickAdd’s token counts are local estimates. Providers enforce the exact limits. For single AI Assistant prompts, QuickAdd logs when the local prompt estimate is above the configured context value, but it still sends the request. The provider may accept it or reject it with a context-window error.
Use these rules when choosing a value:
* For `gpt-4o-mini`, enter `128000`, not a smaller output limit.
* For a local model, use the context window configured for that local model.
* If you do not know the value, import models from the provider if possible, or use the provider’s model documentation.
### Max chunk tokens
[Section titled “Max chunk tokens”](#max-chunk-tokens)
Chunk sizing has no setting of its own. It is the `maxChunkTokens` option of [`chunkedPrompt()`](/docs/QuickAddAPI/#max-chunk-tokens) in the script API.
### Output length
[Section titled “Output length”](#output-length)
The regular Macro AI Assistant command does not have a separate output-length field.
In scripts, `quickAddApi.ai.agent()` accepts `maxOutputTokens` in the agent config or per `generate()` call. QuickAdd maps that option to the provider-specific output field where the provider supports one.
Anthropic requests always require an output token budget. When no explicit `maxOutputTokens` is set, QuickAdd uses the model’s real output limit when the model list carries one (imported and auto-synced models do), and otherwise a conservative default of `4096`.
### Advanced sampling settings
[Section titled “Advanced sampling settings”](#advanced-sampling-settings)
AI Assistant commands expose advanced model parameters:
* **Temperature** controls randomness. Lower values are more focused. Higher values are more varied.
* **Top P** controls nucleus sampling.
* **Frequency penalty** reduces repeated wording on providers that support it.
* **Presence penalty** encourages new topics on providers that support it.
A parameter is only sent when you set it. Untouched settings use the provider’s defaults, and each slider has a reset button that returns it to that state.
Gemini and Anthropic requests use temperature and top P. QuickAdd does not send frequency or presence penalties to those providers.
Fixed-sampling models are handled for you
Many current models use fixed sampling and reject these parameters outright - OpenAI reasoning models and Anthropic’s current generation among them. When a model is known to use fixed sampling, the parameters are not sent. When a provider rejects one anyway, QuickAdd retries the request once without sampling parameters and shows a notice explaining what happened. A sampling slider never hard-fails a command.
## Get the reply into your notes: Macro output variables
[Section titled “Get the reply into your notes: Macro output variables”](#macro-output-variables)
An AI Assistant Macro command stores the model response in the command’s **Output variable name**. The default name is `output`.
If **Output variable name** is `summary`, QuickAdd writes:
* `summary`: the response text
* `summary-quoted`: the same response formatted as a Markdown blockquote, with each line prefixed by `>`
Later commands in the same Macro can use those values:
```markdown
{{VALUE:summary}}
{{VALUE:summary-quoted}}
```
The variables are scoped to that Macro run. A separate QuickAdd choice run does not receive them.
### Example: AI-generated note title
[Section titled “Example: AI-generated note title”](#example-ai-generated-note-title)
Create a prompt-template note named `Title Prompt.md` in your prompt template folder:
```markdown
Generate a short filename-safe title for this text. Reply with only the title.
Text: {{VALUE}}
```
Then use a Macro with two steps:
1. **AI Assistant** command
* Prompt template: `Title Prompt.md`
* Output variable name: `aiTitle`
* Use a low temperature if you want more repeatable titles.
2. **Template** command
* File Name Format: `{{VALUE:aiTitle}}`
* Template body can also include `{{VALUE:aiTitle}}`.
The same pattern works with Capture choices and User Script commands that run after the AI step.
### Read the result in a script
[Section titled “Read the result in a script”](#read-the-result-in-a-script)
When a User Script runs later in the same Macro, read the same variable from `params.variables`:
```js
module.exports = async (params) => {
const description = params.variables.description;
console.log(description);
};
```
If this is empty, check the AI Assistant command’s **Output variable name**. It must be `description`, or your script must read the default `output`.
### Script API assignment is explicit
[Section titled “Script API assignment is explicit”](#script-api-assignment-is-explicit)
`quickAddApi.ai.prompt()` and `quickAddApi.ai.chunkedPrompt()` return an object with the response variables. They write those variables into later Macro steps only when you set `shouldAssignVariables: true` or `assignToVariable`.
```js
module.exports = async ({ quickAddApi }) => {
await quickAddApi.ai.prompt("Summarize the current selection.", "gpt-4o-mini", {
assignToVariable: "summary",
});
};
```
`assignToVariable` also writes `summary-quoted`. Avoid names that are reserved by the formatter or variable plumbing: `value`, `title`, `text`, `meta`, names ending in `-quoted`, names starting with `__qa.`, and names containing `|` or `,`.
## Structured JSON output
[Section titled “Structured JSON output”](#structured-json-output)
Use structured output when you want separate fields from one model response, such as title, summary, and tags. This is a User Script workflow, not the plain Macro AI Assistant command.
The pattern is:
1. User Script calls `quickAddApi.ai.agent().generate({ prompt, schema })`.
2. The script checks `result.object`.
3. The script assigns fields to `params.variables`.
4. A later Template or Capture step uses `{{VALUE:name}}` where each field belongs.
```js
module.exports = async ({ quickAddApi, variables }) => {
const selectedText = quickAddApi.utility.getSelection();
if (!selectedText) {
throw new Error("Select text before running this macro.");
}
const result = await quickAddApi.ai.agent({ model: "gpt-4o-mini" }).generate({
prompt: `Extract a title, a short summary, and up to five tags from this text:\n\n${selectedText}`,
schema: {
type: "object",
properties: {
title: { type: "string" },
summary: { type: "string" },
tags: {
type: "array",
items: { type: "string" },
},
},
required: ["title", "summary", "tags"],
},
});
const data = result.object;
if (!data || typeof data !== "object") {
throw new Error("The AI response did not match the expected JSON shape.");
}
variables.aiTitle = String(data.title ?? "");
variables.aiSummary = String(data.summary ?? "");
variables.aiTags = Array.isArray(data.tags)
? data.tags.map(String).join(", ")
: "";
};
```
Then use a Template step after the script:
```markdown
# {{VALUE:aiTitle}}
{{VALUE:aiSummary}}
Tags: {{VALUE:aiTags}}
```
Structured output supports a small JSON Schema subset: `type`, `properties`, `required`, `items`, `enum`, `const`, `description`, and `title`. Avoid provider-specific schema keywords such as `minLength`, `pattern`, `$ref`, `format`, `anyOf`, and `allOf` in QuickAdd examples.
If the model response does not parse or does not validate, QuickAdd makes one repair attempt. If that still fails, `result.object` is `undefined`, so scripts should handle that as shown above.
## Tool and function calling
[Section titled “Tool and function calling”](#tool-and-function-calling)
QuickAdd 2.14.0 added a script API for tool and function calling:
* `quickAddApi.ai.agent(config)` creates an agent.
* `agent.generate({ prompt })` runs a bounded multi-step loop.
* `quickAddApi.ai.tool(def)` declares a JavaScript function the model may call.
* `quickAddApi.ai.tools.vault()`, `workspace()`, and `system()` provide opt-in built-in tools.
Tools are available from User Scripts. They are JavaScript functions, so they do not live inside a stored AI Assistant Macro command.
```js
module.exports = async ({ quickAddApi }) => {
const agent = quickAddApi.ai.agent({
model: "gpt-4o-mini",
system: "Answer from the user's vault when possible.",
tools: {
...quickAddApi.ai.tools.vault({
only: ["read_note", "search_notes"],
}),
word_count: quickAddApi.ai.tool({
description: "Count words in a text string.",
inputSchema: {
type: "object",
properties: {
text: { type: "string" },
},
required: ["text"],
},
readOnly: true,
execute: ({ text }) => {
const words = String(text ?? "")
.trim()
.split(/\s+/)
.filter(Boolean);
return { count: words.length };
},
}),
},
maxSteps: 12,
});
const result = await agent.generate({
prompt: "What do my notes say about project planning?",
assignToVariable: "answer",
});
return result.text;
};
```
Agent results include:
* `text`: final assistant text
* `object`: structured result, only when `schema` was passed
* `steps`: tool-loop steps
* `toolCalls` and `toolResults`: calls and results from the last step
* `usage`: input, output, and total token counts
* `finishReason`: why the run stopped
By default, agents use up to 20 steps. `maxSteps` is capped at 100.
### Tool approval and safety
[Section titled “Tool approval and safety”](#tool-approval-and-safety)
The global **Confirm AI tool calls** setting defaults to **Destructive tools only (recommended)**:
* `readOnly: true` tools run automatically under the default setting.
* Tools that are not read-only ask for confirmation under the default setting.
* `needsApproval: true` always asks for confirmation.
* **Always confirm every tool** asks for every tool.
* **Never** defers to each tool’s own `needsApproval`.
Tool arguments come from the model - treat them as untrusted
Tool handlers run with the same privileges as your script, and the model chooses tool names and arguments. Validate paths and values, never pass tool input to `quickAddApi.format()`, `eval`, a shell, or a network request without your own checks, and do not put secrets in tool descriptions or arguments because those are sent to the provider.
For the full script API surface, see the [QuickAdd API reference](/docs/QuickAddAPI/#ai-module).
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
### The AI settings button is missing
[Section titled “The AI settings button is missing”](#the-ai-settings-button-is-missing)
Turn off **Disable AI & online features** in QuickAdd settings. The AI settings button is hidden while AI and online features are disabled.
### My model is not listed
[Section titled “My model is not listed”](#my-model-is-not-listed)
Open **AI Assistant settings** > **Edit providers** > your provider > **Edit**, then click **Sync now**. Providers with **Auto-sync models** on pick up new models automatically once a day. You can also browse and import models, or add the model manually - the model name must exactly match what the provider expects.
### A local provider does not respond
[Section titled “A local provider does not respond”](#a-local-provider-does-not-respond)
Check that the local server is running and that the endpoint includes the right base path. For Ollama, use:
```text
http://localhost:11434/v1
```
If the server requires auth, select an API key secret. If the API key is blank, model import sends no `Authorization` header, while OpenAI-compatible chat requests include an empty `Bearer` header.
### Max tokens is confusing
[Section titled “Max tokens is confusing”](#max-tokens-is-confusing)
Use the model’s context-window size. Do not use the model’s advertised output limit. If a request is rejected for context length, shorten the prompt, pick a model with a larger context window, or use chunked prompting.
### A later Macro step cannot read the AI result
[Section titled “A later Macro step cannot read the AI result”](#a-later-macro-step-cannot-read-the-ai-result)
Check these in order:
1. The AI Assistant command and the later command must be steps in the same Macro.
2. The later step must use the AI command’s **Output variable name**.
3. If you did not set a name, read `{{VALUE:output}}`.
4. In a script step, inspect `Object.keys(params.variables)` to see what arrived.
5. If you called `quickAddApi.ai.prompt()` from a script, set `shouldAssignVariables: true` or `assignToVariable`.
### My script does nothing
[Section titled “My script does nothing”](#my-script-does-nothing)
QuickAdd User Scripts must export a function. Put your code inside `module.exports`:
```js
module.exports = async ({ quickAddApi, variables }) => {
const result = await quickAddApi.ai.prompt("Say hello.", "gpt-4o-mini");
variables.output = result.output;
};
```
See the [User Scripts Reference](/docs/UserScripts/).
### Structured output returned no object
[Section titled “Structured output returned no object”](#structured-output-returned-no-object)
`result.object` can be `undefined` when the model fails to return JSON that matches the schema after QuickAdd’s repair attempt. Keep the schema simple, use a current model that supports structured output, and handle the missing object in your script.
### A tool run is waiting forever
[Section titled “A tool run is waiting forever”](#a-tool-run-is-waiting-forever)
The tool probably opened a confirmation modal. In unattended CLI runs, use only `readOnly: true` tools, change **Confirm AI tool calls** for that vault, or design your script so `needsApproval` is not required for that path.
## Workflow ideas
[Section titled “Workflow ideas”](#workflow-ideas)
* **Summarizer:** summarize selected text and capture it to a note.
* **Transform Selected:** rewrite the active selection with a prompt template.
* **AI title:** generate a filename-safe title, then use it in a Template step.
* **Structured note:** extract fields with `agent.generate({ schema })`, then place them in an output template.
* **Vault Q\&A:** use `ai.agent()` with read-only vault tools to answer from notes.
Caution
All provider usage may incur provider costs. Use provider-side spending limits where available.
# Apply Template to Note
> Apply a template to an existing note with insert, append, or replace modes, merging frontmatter and optionally moving the note to match
Created a note manually - with `Cmd/Ctrl+N`, the Quick Switcher, or by clicking a link to a file that didn’t exist yet - and only then realized you forgot to use your template? You don’t need to delete the note and start over. QuickAdd can apply a template to a note you already have.
There are two ways in:
* Run **QuickAdd: Apply template to active note** from the command palette to apply a template to the note currently open.
* Right-click a Markdown file (in the file explorer, a tab header, and so on) and pick **Apply QuickAdd template** to target that file directly.
Either way, you pick a template, choose how it should be added, and QuickAdd fills in the placeholders and merges the frontmatter for you.
## How it works
[Section titled “How it works”](#how-it-works)
1. Run the command while a Markdown note is active.
2. **Pick what to apply.** The picker lists your [Template choices](/docs/Choices/TemplateChoice/) first (including ones nested in Multi choices), followed by template files from your configured templates folder that aren’t already covered by a choice.
3. **Pick how to apply it** (skipped for empty notes - see [Smart behaviors](#smart-behaviors)):
* **Insert at cursor**: inserts the template at the cursor. Only offered when the note is open in the active editor.
* **Insert at top**: inserts the template below the note’s frontmatter, or at the very top if there is none.
* **Append to bottom**: adds the template content to the end of the note.
* **Replace note content**: replaces the entire note with the template.
The template runs through the full QuickAdd [format syntax](/docs/FormatSyntax/) pipeline, and Templater syntax is processed as usual.
Note
Only Markdown templates can be applied. Canvas (`.canvas`) and Base (`.base`) templates hold data for their own file types, so they’re left out of the picker
* use a regular [Template choice](/docs/Choices/TemplateChoice/) to create those. The target note must be Markdown too.
## Smart behaviors
[Section titled “Smart behaviors”](#smart-behaviors)
QuickAdd handles a few common situations for you so you don’t have to think about them:
* **Empty notes skip the prompt.** If the note is empty (or only whitespace), the “how to apply” step is skipped and the template becomes the note’s full content. The usual case is a freshly created blank note.
* **The title fills itself in.** The note already has a name, so `{{TITLE}}` and the unnamed `{{VALUE}}` / `{{NAME}}` resolve to the note’s file name instead of prompting. Named values like `{{VALUE:project}}` still prompt as usual.
* **Frontmatter merges instead of stacking.** For **Insert at top**, **Append to bottom**, and **Insert at cursor**, the template’s frontmatter is not added as a second `---` block. Missing or empty properties are filled from the template. If a property already contains one value, the note’s value wins. Properties that can contain multiple values, such as tags, aliases, and multi-text properties, keep their existing values and add any template values that are not already present. Reapplying the same template does not duplicate them. (**Replace note content** replaces the whole note, frontmatter included.)
* **The note can move to match the choice.** If you picked a Template choice with a folder and/or file name format, and the note’s current location or name doesn’t match what that choice would have produced, QuickAdd offers to move or rename the note to match. Links to the note are updated automatically. This is skipped when the choice’s folder settings need a runtime folder picker, or when a file already exists at the target path.
## From scripts and macros
[Section titled “From scripts and macros”](#from-scripts-and-macros)
The [QuickAdd API](/docs/QuickAddAPI/) does the same thing without any prompts:
```js
await quickAddApi.applyTemplateToActiveFile("templates/meeting.md", {
mode: "top", // "cursor" | "top" | "bottom" | "replace"
});
```
When `mode` is omitted, empty notes get `replace` and non-empty notes get `bottom`.
# Capture
> Add text to any note without opening it: append to your journal, log entries under headings, save links, and capture to Canvas cards
A Capture choice adds text to a note **without opening it**. Press a hotkey, type your entry, and QuickAdd files it exactly where it belongs - while you stay right where you are. Use it to:
* Add timestamped entries to your daily note
* Log work under the right heading of a project note
* Save interesting links for later reading

## Set up your first capture
[Section titled “Set up your first capture”](#set-up)
1. Open **Settings → QuickAdd**, type a name like `Add to journal`, choose **Capture** in the dropdown, and click **Add Choice**.
2. Click the gear (⚙) next to the new choice.
3. Set **Capture To** to where entries should land, for example `Journal/{{DATE}}.md`.
4. Enable **Capture format** and describe one entry, for example `- {{DATE:HH:mm}} {{VALUE}}`.
5. Run it: command palette → `QuickAdd: Run`, pick `Add to journal`, type your entry.
You now have this in today’s journal note:
```markdown
- 09:42 Standup moved to Wednesday
```
Assign the choice a hotkey (⚡ icon, or Obsidian’s Hotkeys settings) once it behaves the way you want.
## Choose where it goes: Capture To
[Section titled “Choose where it goes: Capture To”](#capture-to)
*Capture To* is the note you are capturing to. Either enable **Capture to active file** to write into the note you are currently in, or enter a file path.
The path supports [format syntax](/docs/FormatSyntax/), so it can be dynamic. A daily journal capture might use:
```text
Journal/{{DATE:gggg-MM-DD - ddd MMM D}}.md
```
Every run finds today’s file, and your entry is captured to it.
File names are Markdown-first:
* No extension means a Markdown file: `Inbox` targets `Inbox.md`.
* An explicit supported extension (`.md`, `.canvas`) is kept.
* `.base` files are not supported as capture targets - use a Template choice for `.base` workflows.
Note
If a value used in the file name contains a line break or another control character, QuickAdd folds it to a space and strips trailing spaces or periods from that path segment. The text inserted into the note is not changed.
### How QuickAdd picks the target
[Section titled “How QuickAdd picks the target”](#how-quickadd-picks-a-target)
When **Capture to active file** is off, the resolved *Capture to* value decides what happens:
| You write | What happens |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `Inbox.md` (or any path with an extension) | Captures straight to that file |
| nothing, or `/` | Opens a picker with every Markdown note in the vault |
| `Projects/` (trailing slash) | Opens a picker confined to that folder |
| `Projects` (existing folder, no extension) | Same picker, unless `Projects.md` exists - then the file wins |
| `#people` | Picker with notes carrying that tag |
| `property:type=draft` | Picker with notes whose frontmatter matches - see [capturing to a property](#capturing-to-a-property) |
Paths are vault-relative; a leading `/` is ignored (except a lone `/`, which opens the whole-vault picker).
The picker is ordered like Obsidian’s Quick Switcher: notes you opened most recently come first, then everything else alphabetically. Ordering ignores modification time on purpose, so a sync that touches old notes doesn’t push them to the top. Files in Obsidian’s **Excluded files** list sink to the bottom but stay selectable.
You can also **type a new name** into the picker: with **Create file if it doesn’t exist** enabled, a **Create new note: \** row appears and QuickAdd creates the note for you. The row hides when the typed name matches an existing note in scope, so typing an existing name selects it instead of offering a duplicate. The picker still opens for an empty folder, tag, property, or filtered scope so you can create the first note there.
### Capture to a folder
[Section titled “Capture to a folder”](#capturing-to-folders)
Type a folder name (like `CRM/people`) and QuickAdd asks which note in that folder to capture to - nested folders included. Format syntax works here too.
For example: you keep one note per person in `CRM/people`. Set *Capture To* to `CRM/people`, run the capture, and pick the person. Type `John Doe` instead and QuickAdd creates `CRM/people/John Doe.md` (with **Create file if it doesn’t exist** enabled).
### Capture to a tag
[Section titled “Capture to a tag”](#capturing-to-tags)
Type a tag (like `#people`) and QuickAdd asks which note carrying that tag to capture to.
### Filter by folders and tags together
[Section titled “Filter by folders and tags together”](#capturing-to-filtered-files)
Combine filters with `|` when the destination could live in several folders, or must match several tags:
| You write | The picker shows |
| -------------------------------------------------------- | ---------------------------------- |
| `folder:Goals\|folder:Projects` | Notes in either folder |
| `tag:active\|tag:work` | Notes with **both** tags |
| `folder:Goals\|folder:Projects\|tag:active` | Active notes in either folder |
| `folder:Goals\|exclude-folder:Archive\|exclude-tag:done` | Goals that aren’t archived or done |
Repeated `folder:` filters are OR filters. Repeated `tag:` filters are AND filters. Exclusions remove any matching file.
Capture still writes to **one** destination per run. To select several related notes for metadata, use the [`{{FILE:|multi}}`](/docs/FormatSyntax/#file) placeholder in the capture format instead.
*Introduced in QuickAdd 2.14.0.*
### Capture to notes with a matching property
[Section titled “Capture to notes with a matching property”](#capturing-to-a-property)
Type `property:=` to limit the picker to notes whose frontmatter matches. If your notes have a `type` field, `property:type=draft` opens a picker containing only the notes whose `type` is `draft`.
* `property:type=draft` - notes whose `type` equals `draft`.
* `property:type` - notes that **have** a `type` field, whatever the value.
* Matching is case-insensitive and trimmed. For a list property (`type: [draft, idea]`), the note matches if **any** entry equals the value.
* The value supports [format syntax](/docs/FormatSyntax/): `property:status={{VALUE}}` asks when the capture runs.
Combine with the shared file filters using `|`, the same syntax as [`{{FIELD}}`](/docs/FormatSyntax/#field-filters):
* `property:type=draft|folder:Notes` - only drafts inside `Notes/`.
* `property:type=draft|exclude-folder:Archive` - drafts not in `Archive/`.
* `property:type=draft|exclude-tag:done` - drafts not tagged `#done`.
Good to know:
* Matches **YAML frontmatter** only, not inline Dataview `field:: value` fields.
* The field name matches case-insensitively (`property:type` matches a `Type:` field), and value matching is always case-insensitive.
* Only the `folder:` / `tag:` / `exclude-folder:` / `exclude-tag:` / `exclude-file:` pipe filters are applied here.
* Because `|` starts a filter, a property value cannot itself contain `|`.
* Typing a new note name (with **Create file if it doesn’t exist**) creates the note, but does not automatically give it the property.
*Introduced in QuickAdd 2.14.0.*
### Send one entry to several notes
[Section titled “Send one entry to several notes”](#capturing-the-same-entry-to-multiple-files)
Capture writes to one destination per run. To write the same entry to several fixed notes, compose Capture choices with a [Macro](/docs/Choices/MacroChoice/):
1. Create one Capture choice per destination.
2. Give each the same named value, for example `- {{VALUE:entry}}`.
3. Create a Macro and add each Capture choice as a **Nested Choice** command.
4. Run the Macro: QuickAdd prompts for `entry` once and reuses the answer.
| Choice | Capture To | Format |
| --------------- | -------------------- | ------------------- |
| Log to Person A | `People/Person A.md` | `- {{VALUE:entry}}` |
| Log to Person B | `People/Person B.md` | `- {{VALUE:entry}}` |
If the destinations are dynamic, use one Capture choice with a formatted target and run it repeatedly from a [user script](/docs/UserScripts/):
| Setting | Value |
| ---------- | ---------------------------- |
| Capture To | `People/{{VALUE:person}}.md` |
| Format | `- {{VALUE:entry}}` |
```js
module.exports = async ({ quickAddApi }) => {
const entry = await quickAddApi.inputPrompt("Entry");
const people = await quickAddApi.checkboxPrompt([
"Person A",
"Person B",
"Person C",
]);
for (const person of people) {
await quickAddApi.executeChoice("Log event to person", {
entry,
person,
});
}
};
```
Pass the variables object on every `executeChoice` call - each call clears its temporary variables after the choice runs.
### Friendlier names in the picker
[Section titled “Friendlier names in the picker”](#file-picker-labels)
The picker labels each note by its frontmatter `title` when available, then its first level-1 heading, then its file name. The selected destination is always the real file, so captures write to the same place even when the label is friendlier than the filename.
## Shape the entry: Capture format
[Section titled “Shape the entry: Capture format”](#capture-format)
*Capture format* is what actually gets written - think of it as a mini template for one entry. When disabled, QuickAdd writes `{{VALUE}}`: whatever you type in the prompt (or your editor selection, if selection-as-value is enabled).
All of [format syntax](/docs/FormatSyntax/) works here:
Format
```markdown
- {{DATE:HH:mm}} {{VALUE}}
```
What gets written (after typing "Called the bank")
```markdown
- 09:42 Called the bank
```
For a long format, keep it in a note and reference it:
```text
{{TEMPLATE:Templates/Capture Format.md}}
```
QuickAdd inserts the file’s contents, then processes the result like any capture format - the file can contain `{{VALUE}}`, `{{DATE}}`, `{{MACRO:...}}`, inline scripts, and further `{{TEMPLATE:...}}` includes (`.md`, `.canvas`, and `.base` files; include the extension). This lets you edit, version, and reuse a complete capture format as a normal note. The [one-page input form](/docs/Advanced/onePageInputs/) and `quickadd:check` scan referenced template files too, so their prompts appear up front.
Note
A capture inserts included content as-is. If the referenced template starts with its own `---` frontmatter block and the target note already has one, you get a literal second block - use [Apply Template to Note](/docs/ApplyTemplateToNote/) when frontmatter should merge.
Note
To insert `.base` content into your current note, keep **Capture to active file** enabled and use a `{{TEMPLATE:...}}` placeholder pointing at a `.base` file in the format - see [Capture: Insert a Related Notes Base into an MOC Note](/docs/Examples/Capture_InsertBaseTemplateIntoActiveFile/). To create a brand-new note that embeds a Base, use a Template choice - see [Template: Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/).
If your format includes an inline `js quickadd` block and you need to transform input, read input in script code via `this.quickAddApi.inputPrompt(...)` and assign variables on `this.variables` - don’t put `{{VALUE}}` inside JavaScript string literals. See [Inline scripts](/docs/InlineScripts/#execution-order-and-value).
## The options, one by one
[Section titled “The options, one by one”](#capture-options)
The Capture builder groups its settings into **Location**, **Position**, **Linking**, **Content**, and **Behavior**.
### Create the note if it’s missing
[Section titled “Create the note if it’s missing”](#create-file-if-it-doesnt-exist)
*Create file if it doesn’t exist* does what it says. Optionally create the file **from a template** - an input for the template file appears below the setting.
### Format the entry as a task
[Section titled “Format the entry as a task”](#task)
*Task* formats your captured text as a task (`- [ ] ...`).
### Use your selection as the answer
[Section titled “Use your selection as the answer”](#use-editor-selection)
*Use editor selection as default value* controls whether selected text in the editor is used as `{{VALUE}}` instead of prompting: **Follow global setting**, **Use selection**, or **Ignore selection** (the global default lives in **Settings → Input**). This does not affect `{{SELECTED}}`.
### Pick where in the note it lands: Write position
[Section titled “Pick where in the note it lands: Write position”](#write-position)
*Write position* controls where in the note the entry is written. The options depend on whether **Capture to active file** is enabled:
* **At cursor** (active file) / **Top of file** (target file) - the first option’s label changes with the mode
* **Top of file (after frontmatter)** (active file only)
* **New line above cursor** / **New line below cursor** (active file only)
* **After line…** - insert after a target line you specify, or pick a heading at run time. The workhorse for structured notes - see [Insert after](#insert-after).
* **Before line…** - see [Insert before](#insert-before)
* **Bottom of file**
### Link back to the captured note
[Section titled “Link back to the captured note”](#link-to-captured-file)
*Link to captured file* inserts a link to the note you captured to - useful for leaving a trail in the note you were in. Three modes:
* **Enabled (strict)** - require the configured link destination to be available
* **Enabled (skip if unavailable)** - insert the link when possible, silently skip when nothing is open
* **Disabled** - never insert a link
With either enabled mode, *Link destination* controls where the link goes:
* **Current note** - insert into the active editor
* **Specified note** - append to the bottom of a chosen note (an index or MOC, for example) without opening it. QuickAdd validates the destination before writing. It appends a plain link only: it won’t create the index file, insert under a heading, update properties, or dedupe links.
For **Current note**, strict mode requires a focused Markdown editor (except Canvas-triggered captures, which skip link insertion when no Markdown editor is available).
#### Where the link is placed
[Section titled “Where the link is placed”](#link-placement)
For the **Current note** destination, *Link placement* chooses the spot:
* **Replace selection** - replace any selected text with the link (default)
* **After selection** - keep the selected text, place the link after it
* **End of line** - at the end of the current line
* **New line** - on a new line below the cursor
* **In frontmatter property** - add the link to a named frontmatter property
For **In frontmatter property**, set the property name and how strictly to handle missing or non-list properties:
* **Create or convert** (default) - create the property if missing, or convert an existing scalar value into a list before appending. Object values still error.
* **Create if missing** - create the property if missing; existing scalar/object values error.
* **Require list** - append only to an existing list property. Empty/null count as empty lists; missing properties and scalar/object values error.
Note
If your cursor is in an editable Obsidian Properties field when the capture starts (and placement isn’t **In frontmatter property**), QuickAdd appends the link to that focused property instead of using the stale editor cursor behind the Properties panel. Text properties get the link at the end of the value; list properties get a new item.
#### Link or embed
[Section titled “Link or embed”](#link-type)
For the body placements (**Replace selection**, **After selection**, **End of line**, **New line**), a *Link type* dropdown chooses **Link** (`[[Note]]`) or **Embed** (`![[Note]]`). An embed transcludes the captured note’s contents at the placement position. **In frontmatter property** and the **Specified note** destination stay link-only.
#### What the link says
[Section titled “What the link says”](#link-display-text)
For the selection placements with the **Link** type, *Link display text* chooses the visible text. **Selected text** keeps your highlight as the display text: selecting `Meeting with Mark` and capturing to `20240101 Meeting with Mark` inserts `[[20240101 Meeting with Mark|Meeting with Mark]]`. With nothing selected (or a selection that can’t sit safely inside a link), the plain link is inserted. Multi-line selections collapse to one line, and vaults using Markdown-style links get `[Meeting with Mark](20240101%20Meeting%20with%20Mark.md)`.
### Copy a link to the clipboard
[Section titled “Copy a link to the clipboard”](#copy-link-to-clipboard)
*Copy link to clipboard* copies a link to the captured note after the capture runs - independent of *Link to captured file*, so you can copy without inserting, or do both. The copied link is a vault-path wikilink, ready to paste into another note.
### Open the captured note
[Section titled “Open the captured note”](#opening-the-captured-file)
When **Capture to active file** is off, the **Behavior** section shows an *Open* toggle. Enabling it reveals:
* *File opening location* - **Reuse current tab**, **New tab**, **Split pane**, **New window**, **Left sidebar**, or **Right sidebar**
* *Split direction* - **Split right** or **Split down** (shown for **Split pane**)
* *View mode* - **Source**, **Preview**, **Live Preview**, or **Default**
* *Focus new pane* - focus the opened tab immediately (shown for every location except **Reuse current tab**)
When QuickAdd opens and focuses a Markdown target in an editable mode, it places the cursor at the end of the inserted capture so you can keep typing. This is skipped for preview/unfocused opens and when Templater cursor markers take over.
### Run Templater on the whole file afterwards
[Section titled “Run Templater on the whole file afterwards”](#run-templater-on-entire-destination-file-after-capture)
*Run Templater on entire destination file after capture* is an advanced, legacy option: it executes any `<% %>` anywhere in the destination file, including inside code blocks. Leave it off unless you specifically need that whole-file pass.
### Templater and newly created notes
[Section titled “Templater and newly created notes”](#templater-and-newly-created-files)
Capture has two Templater paths when it creates a missing Markdown file:
* **Create file if it doesn’t exist** without a QuickAdd template: QuickAdd creates a blank file first. If Templater’s new-file trigger applies to that location, QuickAdd waits for Templater to finish before inserting the capture.
* **Create with template**: QuickAdd owns the initial content. It renders the selected QuickAdd template, suppresses Templater’s new-file/directory trigger for that creation, then runs Templater once on the content QuickAdd wrote.
So a blank Capture-created file can receive Templater’s directory template first, while a template-created file runs Templater on QuickAdd’s template content instead.
## Insert after
[Section titled “Insert after”](#insert-after)
**After line…** inserts the entry after a line with the text you specify - this is how entries land under the right heading. A journal capture might insert after `## What did I do today?`.
By default, QuickAdd preserves blank lines after headings to keep spacing intact. **Blank lines after match** controls this:
* **Auto (headings only)** - skip blank lines only when the matched line is a heading
* **Always skip** - skip all consecutive blank lines after the match
* **Never skip** - insert immediately after the matched line
Example (Auto, insert after `# H` with content `X`):
```markdown
# H
X
A
```
With Insert after you can also enable **Insert at end of section** and **Consider subsections** - see [below](#consider-subsections--option).
**Create line if not found** creates the target line when it doesn’t exist - useful when the heading might not be in the note yet. The created line can go at the **Top** or **Bottom** of the file, at your **Cursor**, or **Ordered** - sorted among same-level headings; see [Ordered section placement](#ordered-section-placement) for reverse-chronological logs and changelogs.
The target may span several lines: type `\n` in the **Insert after** field to match a multi-line anchor (the preview shows it expanded). **Inline insertion** is the exception - it inserts on the same line, so its target must be a single line; a `\n` target there is rejected with a notice.
### Ordered section placement
[Section titled “Ordered section placement”](#ordered-section-placement)
When **Create line if not found** is set to **Ordered** (full label: `Ordered (place new section among siblings)`), a missing “Insert after” heading is created at its **sorted position among same-level headings**. This is the building block for a reverse-chronological log: each new dated section is added above older ones, while a fixed title stays pinned at the top.
The classic “daily log, newest first” recipe (issue [#481](https://github.com/chhoumann/quickadd/issues/481)):
* **Capture to**: your log note (enable `Create file if it doesn't exist` to auto-create it)
* **Format**: the entry with a trailing newline, e.g. `- {{DATE:HH:mm}} {{VALUE}}\n` (task captures add their own newline)
* **Insert after**: the day heading, `## {{DATE:YYYY-MM-DD}}`
* **Insert at end of section**: off, so each entry lands directly under the day heading (newest first within the day)
* **Create line if not found**: on, location **Ordered**, **Sort sections by** = `Date`, **Section order** = `Newest / highest first`
Running it on consecutive days (and twice in one day) produces:
```markdown
# My Daily Log
A short intro that always stays at the top.
## 2026-06-16
- 09:40 reviewed the code
- 09:13 started the design
## 2026-06-14
- 09:00 older entry
```
The first capture of a day creates `## 2026-06-16` below the intro and above `## 2026-06-14`; later captures that day find the existing heading and add their entry on top. The `# My Daily Log` title stays put because only same-level headings (`##`) are sorted against each other, and YAML frontmatter is never treated as a heading.
#### Sort options
[Section titled “Sort options”](#sort-options)
With **Ordered** selected, these controls appear:
* **Sort sections by** - how the sort key is read from each heading:
* `Insertion order (no sorting)` - newest-first prepends the new section; oldest-first appends it. No parsing.
* `Text (A→Z)` - case-insensitive text compare.
* `Number` - the leading number in the heading (e.g. `## 12 Project X`).
* `Date` - parsed with a **Date format** (auto-detected from the `{{DATE:…}}` placeholder in your “Insert after” text, and editable). Trailing decoration like `## 2026-06-14 (Friday)` is ignored.
* `Version (semver)` - `major.minor.patch`, so `1.10.0` sorts above `1.9.0`. A leading `v` and the Keep-a-Changelog `## [1.10.0] - 2026-06-16` form are both understood.
* **Section order** - `Newest / highest first` or `Oldest / lowest first`.
* **Existing unparseable headings** (for `Date`, `Number`, `Version (semver)`) - where to rank headings that can’t be parsed for the chosen key (like `## Unreleased` in a changelog): sort to bottom (default) or top. A new heading that can’t be parsed is always appended at the end.
Use **Insert at end of section** to control order *within* each section: off = newest entry on top (date logs), on = entries appended at the end (changelogs).
#### More examples
[Section titled “More examples”](#more-examples)
A changelog with the newest version on top - **Insert after** `## {{VALUE:version}}`, **Format** `- {{VALUE:change}}\n`, **Insert at end of section** on, **Create line if not found** on → **Ordered**, **Sort by** `Version (semver)`, **Order** `Newest / highest first`:
```markdown
# Changelog
## 1.10.0
- new feature
- another fix in 1.10.0
## 1.9.0
- old fix
```
A “books read” note grouped by year - **Insert after** `## {{DATE:YYYY}}`, **Format** `- {{VALUE}}\n`, **Create line if not found** on → **Ordered**, **Sort by** `Date` with **Date format** `YYYY`, **Order** `Newest / highest first`.
#### Notes and limits
[Section titled “Notes and limits”](#notes-and-limits)
* The heading is created **once** and reused - every later capture finds it, so the section is never duplicated. This relies on the heading resolving to the **same text** each time: use a stable placeholder (`{{DATE:…}}`, a `{{VALUE:…}}` you supply), not a random one.
* Sorting covers **all same-level headings in the note**, not one parent section. For the layouts above (dated/versioned `##` sections under one `#` title) that’s exactly right - keep the fixed title at a different heading level so it’s never a sortable sibling.
* Ordered placement positions the **new** section only; it doesn’t re-sort existing ones.
* **Ordered** is for headings and can’t be combined with **Inline insertion**.
*Introduced in QuickAdd 2.14.0.*
### Choose the heading when capturing
[Section titled “Choose the heading when capturing”](#choose-heading-when-capturing)
Instead of typing the target line when you build the choice, enable **Choose heading when capturing** to pick it **at run time**: QuickAdd reads the target note and shows a dropdown of its headings - pick one, and the entry is inserted under it. Useful when the heading varies between runs, or when you’d rather not remember it.
The picked heading simply becomes the insert-after target, so every placement control still applies: **Insert at end of section**, **Consider subsections**, **Blank lines after match**, and **Create line if not found** work exactly as usual.
Good to know:
* You can type a heading that doesn’t exist yet; enable `Create line if not found` to have QuickAdd create it (type it with its `#` markers, e.g. `## Tasks`).
* The dropdown lists ATX headings (lines starting with `#`). For a brand-new note created from a template, the picker can’t list the template’s headings (the note doesn’t exist at pick time) - type the heading and use `Create line if not found`.
* With the one-page input form, the heading dropdown still appears as a separate step after the form.
*Introduced in QuickAdd 2.14.0.*
### Consider subsections
[Section titled “Consider subsections”](#consider-subsections--option)
Controls whether a section’s nested subsections count as part of it when using **Insert at end of section**.
Disabled - the section ends where its first subsection starts:
```markdown
## 1. First heading
**Insert after** comes here.
- content 1
- content 2
- content 3
**Insert at end** comes here.
### 1.1. Nested heading 1
Content
## 2. Another heading
Content
```
Enabled - subsections belong to the section, so “end of section” is after them:
```markdown
## 1. First heading
**Insert after** comes here
- content 1
- content 2
- content 3
### 1.1. Nested heading 1
Content
**Insert at end** comes here. Captures to after this, as it's considered part of the "1. First heading" section.
## 2. Another heading
Content
```
## Insert before
[Section titled “Insert before”](#insert-before)
**Before line…** inserts the capture before the first line matching the text you specify. The target accepts [format syntax](/docs/FormatSyntax/), so values like `{{TITLE}}` and `{{LINKCURRENT}}` work in the match text.
**Create line if not found** works here too: QuickAdd writes the captured content first, then creates the missing line below it. The created line can go at the start or end of the file, or at your cursor.
## Capture to Canvas
[Section titled “Capture to Canvas”](#canvas-capture-notes)
QuickAdd supports two Canvas capture workflows:
* Capture to the selected card in the active Canvas view
* Capture to a specific card in a specific `.canvas` file
### Capture to the selected card
[Section titled “Capture to the selected card”](#1-capture-to-selected-card-in-active-canvas)
Enabled when **Capture to active file** is on and the active view is a Canvas. Supported card targets:
* Text cards
* File cards that point to Markdown files
### Capture to a card in a specific file
[Section titled “Capture to a card in a specific file”](#2-capture-to-specific-card-in-specific-canvas-file)
Enabled when **Capture to active file** is off, the capture path resolves to a `.canvas` file, and **Target canvas node** is set. When the path is a `.canvas` file, QuickAdd shows a node picker so you can choose the card directly from that board.
### Write positions in Canvas
[Section titled “Write positions in Canvas”](#write-position-support-in-canvas)
* Text cards and file cards (Markdown targets) support: **Top of file**, **Bottom of file**, **After line…**, **Before line…**
* Cursor-based modes (**At cursor**, **New line above/below cursor**) don’t exist in Canvas. If **Capture to active file** is on and the write position is still the default **At cursor**, the capture aborts until you switch to a supported mode.
Selected-card mode needs exactly one selected card. If the selection is missing, multiple, or unsupported, QuickAdd aborts with a notice instead of writing to the wrong place.
When append-link is **Enabled (requires active file)** and the capture runs from a Canvas card without a focused Markdown editor, the capture still writes and link insertion is skipped.
For a step-by-step setup, see [Capture: Canvas Capture](/docs/Examples/Capture_CanvasCapture/).
### Canvas capture FAQ
[Section titled “Canvas capture FAQ”](#canvas-capture-faq)
**Why did my capture abort in Canvas?** Most often: no card selected, more than one card selected, an unsupported card type, or a cursor-based write position.
**Can I target a specific card in a Canvas file?** Yes - set the capture path to a `.canvas` file and choose a **Target canvas node**.
**Does “At cursor” work in Canvas cards?** No. Use top, bottom, insert-after, or insert-before placement.
**Can I capture to a file card that points to a Canvas file?** No - file-card capture supports Markdown targets only.
**Can I still create new Canvas files from templates?** Yes. Template choices support `.canvas` templates.
# Macros
> Chain Obsidian commands, user scripts, nested choices, AI steps, and conditionals into one automated command that shares data between steps
A macro chains several QuickAdd actions into one command you can run from the palette or a hotkey. Instead of running a template, then a capture, then a script by hand, a macro runs them in order and passes data from one step to the next. Reach for a macro when a single choice isn’t enough. Use it to:
* Ask a question once and reuse the answer across several steps
* Run your own JavaScript to talk to the Obsidian API or another plugin
* Branch the workflow based on what you picked or what a script returns
* Kick off a routine automatically when Obsidian starts
Macros are QuickAdd’s most capable - and most technical - choice type. You don’t need to be a programmer to start: the walkthrough below uses no code at all. The deeper sections assume you’re comfortable with a little JavaScript.
Tip
Once you have a macro (or a whole collection of choices) that you love, use the [QuickAdd package exporter](/docs/Choices/Packages/) to bundle it with its dependencies and share the `.quickadd.json` file with other vaults.
## What is a macro?
[Section titled “What is a macro?”](#what-are-macros)
A **macro** is a list of commands that run one after another. Each macro is paired with a **macro choice**, the entry that shows up in the QuickAdd menu and gives you something to trigger.
### The pieces
[Section titled “The pieces”](#key-concepts)
* **Macro choice** - the trigger that appears in the QuickAdd menu.
* **Macro** - the actual sequence of commands that runs.
* **Commands** - the individual steps (Obsidian commands, scripts, AI prompts, and more).
* **Variables** - data that one command sets and a later command reads, all within a single run.
## Set up your first macro
[Section titled “Set up your first macro”](#creating-a-macro)
We’ll build a tiny macro with no code: it opens today’s daily note and drops your cursor at the end, ready to type. Two commands, run as one.
### Step 1: Create the macro choice
[Section titled “Step 1: Create the macro choice”](#step-1-create-a-macro-choice)
1. Open **Settings → QuickAdd**, type a name like `Open daily note`, choose **Macro** in the dropdown, and click **Add Choice**.
2. Click the gear (⚙) next to the new choice to open the Macro Builder.

### Step 2: Build the macro
[Section titled “Step 2: Build the macro”](#step-2-build-your-macro)
1. In the Macro Builder, add an **Obsidian Command** and pick `Daily notes: Open today's daily note`.
2. Add an **Editor commands** entry and choose **Move cursor to file end**.
3. Close the builder, then run it: command palette → `QuickAdd: Run` → `Open daily note`.
Your daily note opens and the cursor sits at the end of the file, ready for the next line - both steps in a single command. Assign the choice a hotkey (the ⚡ icon, or Obsidian’s Hotkeys settings) once it behaves the way you want.
## The commands you can add
[Section titled “The commands you can add”](#command-types)
The Macro Builder offers these command types. Add as many as you like, in any order.
| Command | What it does |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Obsidian Command** | Run any Obsidian command, for example `Daily notes: Open today's daily note` or `Toggle reading view`. |
| **Editor commands** | Manipulate text in the active editor: copy, cut, paste, [paste with format](#paste-with-format), select the line or a link on it, and move the cursor. See [Editor commands](#editor-commands). |
| **User Script** | Run your own JavaScript to reach the Obsidian API, do complex work, or integrate with other plugins. See [Add a user script command](#add-a-user-script-command). |
| **Nested Choice** | Run another QuickAdd choice - a template, capture, or another macro - so you can reuse existing work and build modular workflows. |
| **Wait** | Pause for a set number of milliseconds, useful when a previous command needs time to finish. |
| **AI Assistant** | Run an AI prompt to generate or process content. Available once you’ve configured an AI provider. |
| **Open File** | Open an existing file at a formatted path. Supports all [format syntax](/docs/FormatSyntax/) (`{{DATE}}`, `{{VALUE}}`, and so on), with tab and split options. It opens in the default view mode with focus, and only opens files that already exist (it won’t create one). |
| **Conditional** | Branch the run based on live data. See [Branch with a conditional](#conditional-commands). |
### Add a user script command
[Section titled “Add a user script command”](#add-a-user-script-command)
Macros don’t contain JavaScript directly. Your code lives either in a `.js` file inside your vault **or** in a ` ```js ` code block inside a note, and the macro simply runs it. The note option is handy on mobile, where Obsidian cannot open `.js` files - see [User Scripts](/docs/UserScripts/#scripts-in-a-note-code-block).
Create a script file such as `scripts/my-macro.js`, or a note such as `Scripts/my-macro.md` with your code in a ` ```js ` (or ` ```javascript `) block. QuickAdd runs the **first** matching JavaScript block in a note and ignores the surrounding prose.
To add it, open the Macro Builder and add a **User Script** command. There are two ways to point it at your script:
* **Browse** opens QuickAdd’s script picker (not your operating system’s file picker). It lists the `.js` files and notes-with-a-code-block that Obsidian has already discovered, so it can’t reach files outside the vault or hidden from Obsidian’s index.
* **Type it in.** For a `.js` file, type its basename - for `scripts/my-macro.js`, enter `my-macro`. For a note, type its vault path, for example `Scripts/my-macro.md`. Then click **Add**. To run a specific exported function, append it with `::`, such as `my-macro::start`.
If the script exports more than one function and you don’t name one, QuickAdd asks which export to run. You can also set an output variable name so later commands can reuse the result.
Where to keep scripts
Keep the script inside your vault, but **not** inside `.obsidian` or any folder whose name starts with a dot. Obsidian may exclude hidden folders from its file index, and QuickAdd builds the picker from Obsidian’s indexed files, so a hidden script never shows up. Use a normal folder such as `scripts/`, or a visible underscore-prefixed folder such as `_quickadd/scripts/`. Full rules are in [User Scripts](/docs/UserScripts/#adding-scripts-to-macros).
Good to know:
* To **insert text into a note**, don’t write it in a script. Use a **Template** or **Capture** choice and run it from the macro as a **Nested Choice** command. That’s the intended way to write content, and no YAML frontmatter is required.
* If your script calls the API of another plugin, that plugin must be installed and enabled in your vault. You don’t need any extra plugin just to run user scripts.
### Branch with a conditional
[Section titled “Branch with a conditional”](#conditional-commands)
A conditional command lets your macro take one path or another without writing boilerplate JavaScript. Each conditional has:
* **Condition mode** - compare a macro variable, or run a script that returns `true`/`false`.
* **Variable comparisons** - test a variable with operators like equals, contains, less than, greater than, or a basic truthiness check. The value type (text, number, boolean) controls how the two sides are compared.
* **Script mode** - point to a JavaScript file in your vault (with an optional exported function) that returns a boolean. The script gets the same parameters as any user script, including your macro variables and `params.abort`.
* **Branch editors** - the commands that run when the condition passes (**Then**) or fails (**Else**). Each branch is a full command sequence, so you can nest more conditionals or reuse any command type.
To add one:
1. Click the branch icon in the command bar of the Macro Builder (or of any conditional branch editor).
2. Click the settings icon on the new command to define the condition.
3. Use the branch buttons to set the commands that run for the **Then** and **Else** outcomes.
The macro runs the matching branch in order, then continues with the rest of the macro. Branch commands share the same variable map as the outer macro, so they can read or update variables for later steps.
## Editor commands
[Section titled “Editor commands”](#editor-commands)
Editor commands manipulate text in the active editor.
### Paste with format
[Section titled “Paste with format”](#paste-with-format)
**Paste with format** preserves rich formatting when you paste from an external source. Unlike the standard paste, which handles plain text only, it:
* **Detects HTML** in your clipboard
* **Converts it to Markdown** using Obsidian’s built-in conversion
* **Preserves formatting** like links, bold, italics, headers, and lists
* **Falls back gracefully** to plain text when no HTML is available
What that looks like in practice:
| You copy | You paste |
| ------------------------------- | ---------------------------------- |
| A formatted link from a webpage | `[Link Text](https://example.com)` |
| Text with bold/italic | **bold** and *italic* preserved |
| A bulleted list | A proper Markdown list |
| A table from a website | A Markdown table |
Note
Paste with format uses modern clipboard APIs, with an automatic fallback for older versions.
### The other editor commands
[Section titled “The other editor commands”](#other-editor-commands)
* **Copy / Cut / Paste** - standard clipboard operations.
* **Select active line** - select the whole line the cursor is on.
* **Select link on active line** - find and select a link on the current line.
* **Move cursor to file start / file end** - jump to the beginning or end of the file.
* **Move cursor to line start / line end** - jump to the beginning or end of the current line.
## User scripts
[Section titled “User scripts”](#user-scripts)
A user script extends a macro with custom JavaScript, written either in a `.js` file or in a ` ```js ` code block inside a note. Scripts have access to:
* The Obsidian `app` object
* The QuickAdd API
* A `variables` object for passing data between commands
[]()
The basic shape is an exported async function - QuickAdd calls it with a `params` object that carries everything you need:
```javascript
module.exports = async (params) => {
// Destructure the parameters
const { app, quickAddApi, variables } = params;
// Your code here
console.log("Hello from my macro!");
// Set a variable for use in later commands
variables.myResult = "Some value";
};
```
[]()[]()[]()[]()
Everything else about writing scripts lives in the [User Scripts reference](/docs/UserScripts/):
* [Where a script can live](/docs/UserScripts/#adding-scripts-to-macros) - `.js` file or note code block, and which folders QuickAdd’s picker can see.
* [Prompt the user](/docs/UserScripts/#user-input) - input prompts, suggesters, yes/no, and checkbox prompts via `quickAddApi`; the full method list is in the [QuickAdd API](/docs/QuickAddAPI/).
* [Read the editor selection](/docs/QuickAddAPI/#getselection-string) - `quickAddApi.utility.getSelection()`.
* [Reach into other plugins](/docs/UserScripts/#accessing-other-plugins) - talk to Templater, MetaEdit, or any plugin through `app.plugins.plugins`.
* [Offer several actions from one script](/docs/UserScripts/#multiple-entry-points) - export more than one function and pick at run time.
* [Configurable settings](/docs/UserScripts/#configurable-options), [error handling and `abort()`](/docs/UserScripts/#error-handling-and-macro-control), and a shelf of [copy-paste recipes](/docs/UserScripts/#common-patterns--recipes).
## Pass data between commands: variables
[Section titled “Pass data between commands: variables”](#variables-and-data-flow)
Every command in a macro shares one temporary variable map for the current run. A user script can write `params.variables.bookTitle`, and a later Template or Capture command can read it back as `{{VALUE:bookTitle}}`.
For the full rules - named `VALUE` prompts, empty values, AI Assistant output variables, and the `executeChoice` boundary - see [Variables and data flow](/docs/VariablesDataFlow/).
[]()
## Run one export directly: `Macro::member`
[Section titled “Run one export directly: Macro::member”](#direct-function-access)
When a script [exports several functions](/docs/UserScripts/#multiple-entry-points), QuickAdd normally asks which one to run. You can skip that prompt by naming the function:
* `{{MACRO:MyMacro::option1}}` runs `option1` directly.
* `{{MACRO:MyMacro::start}}` runs the `start` function.
When a macro has more than one user script, `Macro::member` picks the script that uniquely exports the requested member across all scripts in the macro. QuickAdd resolves it like this:
* If exactly one script exports the member, QuickAdd uses it.
* If no script exports the member, QuickAdd stops and shows an error.
* Exception: if the macro has no user-script commands at all, QuickAdd can’t satisfy member access - it logs a warning and returns an empty result instead of stopping the macro.
* If several scripts export the member, QuickAdd stops and lists the conflicting script names instead of guessing.
* Exception: the convention keys `settings`, `entry`, and `quickadd` (which many scripts export as metadata rather than entrypoints) resolve to the **first** script that exports them and show a one-time notice pointing at the selector form below, rather than stopping. Use the selector if you need a different script.
When there’s a conflict, target a specific script by name:
* `{{MACRO:MyMacro::Script 1::option1}}`
The selector uses the macro command name shown in the editor. If two user-script commands share the same name, rename one before using the selector form.
## Macro settings
[Section titled “Macro settings”](#macro-settings)

### Run on startup
[Section titled “Run on startup”](#run-on-startup)
Enable this to run a macro automatically when Obsidian starts. Handy for:
* Creating a daily note automatically
* Setting up your workspace
* Running maintenance tasks
## Practical examples
[Section titled “Practical examples”](#practical-examples)
### Example 1: Log a book to your daily note
[Section titled “Example 1: Log a book to your daily note”](#example-1-book-logging-macro)
Prompt for a book name and write it into today’s daily note (using the MetaEdit plugin):
```javascript
module.exports = async (params) => {
const { quickAddApi: { inputPrompt }, app } = params;
// Get book name from user
const bookName = await inputPrompt("📖 Book Name");
// Get MetaEdit plugin
const { update } = app.plugins.plugins["metaedit"].api;
// Format today's date
const date = window.moment().format("YYYY-MM-DD");
// Update the daily note
await update("Book", bookName, `Daily Notes/${date}.md`);
};
```
### Example 2: Create a task with priority
[Section titled “Example 2: Create a task with priority”](#example-2-task-management-macro)
Ask for a task and a priority, then hand them to a later Template command as variables:
```javascript
module.exports = async (params) => {
const { quickAddApi, app, variables } = params;
// Get task details
const task = await quickAddApi.inputPrompt("Task description:");
const priority = await quickAddApi.suggester(
["🔴 High", "🟡 Medium", "🟢 Low"],
["high", "medium", "low"]
);
// Set variables for use in template
variables.taskDescription = task;
variables.taskPriority = priority;
variables.taskCreated = new Date().toISOString();
// Create task note using template (in next macro command)
};
```
### Example 3: Scaffold a research workspace
[Section titled “Example 3: Scaffold a research workspace”](#example-3-research-workflow)
Chain several operations: create a folder structure for a topic, then set variables for a later template step to fill an overview note.
```javascript
module.exports = async (params) => {
const { quickAddApi, app, variables } = params;
// Get research topic
const topic = await quickAddApi.inputPrompt("Research topic:");
// Create folder structure
const vault = app.vault;
const researchFolder = `Research/${topic}`;
// Check if folder exists
if (!await vault.adapter.exists(researchFolder)) {
await vault.createFolder(researchFolder);
await vault.createFolder(`${researchFolder}/Sources`);
await vault.createFolder(`${researchFolder}/Notes`);
}
// Set variables for template
variables.researchTopic = topic;
variables.researchFolder = researchFolder;
// Next commands in macro will create the overview note
};
```
## When a macro stops
[Section titled “When a macro stops”](#macro-execution-control)
### What stops a macro
[Section titled “What stops a macro”](#automatic-abort-behavior)
A macro stops early in three situations:
1. **You cancel** - press Escape or click Cancel in any prompt.
2. **A script errors** - an unhandled error is thrown in a user script.
3. **A script aborts on purpose** - `params.abort()` is called.
When a macro stops:
* Every remaining command is skipped.
* A message is logged explaining why.
* For your own cancel and for explicit aborts, no error dialog appears.
* For a script error, the full error and stack trace are kept for debugging.
## Best practices
[Section titled “Best practices”](#best-practices)
### 1. Handle errors
[Section titled “1. Handle errors”](#1-error-handling)
Wrap script work in `try`/`catch` so a failure is visible and stops the rest of the macro:
```javascript
module.exports = async (params) => {
try {
// Your code here
} catch (error) {
console.error("Macro error:", error);
new Notice(`Macro failed: ${error.message}`);
throw error; // Re-throw to stop remaining macro commands
}
};
```
### 2. Check for plugin dependencies
[Section titled “2. Check for plugin dependencies”](#2-check-for-plugin-dependencies)
Confirm a required plugin is present before you use it:
```javascript
module.exports = async (params) => {
const { app } = params;
const requiredPlugin = app.plugins.plugins["plugin-id"];
if (!requiredPlugin) {
new Notice("Required plugin not found!");
return;
}
// Continue with plugin operations
};
```
### 3. Use meaningful variable names
[Section titled “3. Use meaningful variable names”](#3-use-meaningful-variable-names)
Descriptive names keep a macro readable:
* ✅ `variables.projectName`
* ✅ `variables.meetingDate`
* ❌ `variables.var1`
* ❌ `variables.temp`
### 4. Keep it modular
[Section titled “4. Keep it modular”](#4-modular-design)
Break a complex macro into smaller, reusable parts:
* Put distinct operations in separate scripts.
* Reuse existing choices with **Nested Choice** commands.
* Keep each script focused on a single purpose.
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
### Common issues
[Section titled “Common issues”](#common-issues)
**“Syntax error: unexpected identifier”**
* Usually a JavaScript syntax error in your script.
* Check for a missing semicolon, bracket, or quote.
* See [issue #417](https://github.com/chhoumann/quickadd/issues/417) for detailed solutions.
**“Cannot read property of undefined”**
* A plugin or API you’re reaching for doesn’t exist.
* Add a null check before you use a plugin’s API.
* Make sure the plugin is enabled before you run the macro.
**Variables not passing between commands**
* Use a named placeholder such as `{{VALUE:sharedName}}`, or set `params.variables.sharedName`, for values later steps need.
* Make sure the script runs *before* the command that reads its variables.
* See [Variables and data flow](/docs/VariablesDataFlow/) for the full model.
**Macro not appearing in command palette**
* Make sure the macro choice is enabled in settings.
* Restart Obsidian if you just created the macro.
* Check that QuickAdd is enabled in Community Plugins.
## Tips and tricks
[Section titled “Tips and tricks”](#tips-and-tricks)
1. **Test incrementally** - build the macro one command at a time, testing each.
2. **Use `console.log`** - log values to the developer console while debugging.
3. **Keep scripts in your vault** - so you can version and back them up.
4. **Share macros** - export and import macro configurations with other users.
5. **Combine with hotkeys** - assign a shortcut to a macro you run often.
## See also
[Section titled “See also”](#see-also)
* [Template Choices](/docs/Choices/TemplateChoice/) - for creating new notes
* [Capture Choices](/docs/Choices/CaptureChoice/) - for appending to existing notes
* [Format Syntax](/docs/FormatSyntax/) - available placeholders
* [QuickAdd API](/docs/QuickAddAPI/) - detailed API documentation
* [Examples](/docs/Examples/Macro_BookFinder/) - pre-built macro examples
# Multis
> Group choices into collapsible folders in the QuickAdd picker, with placeholder text, custom icons, and search across nested choices
A Multi is a **folder for your other choices**. Group related choices under one entry in the QuickAdd picker, then open it to see what’s inside - handy once your picker grows past a handful of items. In the settings list, a Multi is the entry you can fold and unfold.

## Put choices inside a multi
[Section titled “Put choices inside a multi”](#add-choices)
You add a choice to a multi by **dragging it in**. Make sure the multi is unfolded (as in the screenshot above), grab the drag handle of the choice you want to move, and drop it just below and slightly to the right of the multi’s own drag handle. When it works, the choice appears indented under the multi.
Tip
The first choice is the fiddly one, since there’s nothing nested yet to aim for. Drop it just below and to the right of the multi’s drag handle and watch for the indent. Once one choice is inside, the rest are easy.
## Set the search box hint: Placeholder text
[Section titled “Set the search box hint: Placeholder text”](#placeholder-text)
Each multi can show its own hint in the choice picker’s search box when you open it - useful for labeling a complex menu or a grouped workflow. Leave it empty and the multi’s name is used instead.
Because [search reaches everything nested under the multi](#searching-nested-choices), word the hint for the whole group, not just the top level.
## Change a choice’s icon
[Section titled “Change a choice’s icon”](#icons)
Choices in the QuickAdd picker use the same Obsidian/Lucide icons as registered QuickAdd commands. Every choice type has a default icon, and you can override it from the choice’s **Icon** setting. Icons are monochrome and take on your active Obsidian theme color.
## Search across nested choices
[Section titled “Search across nested choices”](#searching-nested-choices)
Typing in the choice picker searches every choice nested inside the current level’s multis, not just the level you are looking at. A nested match shows its folder path (for example `Work / Meetings`) beneath the choice name. This also applies to the root picker opened by the **QuickAdd: Run** command or the ribbon icon.
Good to know:
* Browsing is unchanged: with an empty search box, you still see one level at a time.
* The search also matches the folder path, so `work meeting` finds `New meeting` inside `Work / Meetings`.
* Selecting a nested multi from the results opens it. Its **← Back** entry returns to the level you searched from, skipping any levels in between.
* Searching from inside a multi only covers that multi’s sub-choices. Go back (or open the root picker) to search more broadly.
To limit search to the level you have open, turn off **Settings → QuickAdd → Search nested choices**.
# Share QuickAdd Packages
> Bundle choices, macros, and scripts into a .quickadd.json file to move between vaults, with a capability review before importing
A package bundles choices, macros, and their supporting scripts into a single `.quickadd.json` file. Use one to move a workflow to another vault or share it with someone else - they import the file and get your choices without rebuilding anything by hand. Importing shows a full review of what the package can do first, so you always see the scripts and macros before they run.
## Export a package
[Section titled “Export a package”](#export-a-package)
1. Open **Settings → QuickAdd** and scroll to the choices list.
2. Click **Export package…** in the Packages setting.
3. Use the filter to find the choices you want to share, then tick their checkboxes. Any dependent choices or scripts are added automatically.
4. Review the summary panel to confirm how many choices and assets are included.
5. Choose **Copy JSON** (puts the package on your clipboard) or **Save to file**. When saving, QuickAdd creates any missing folders inside your vault automatically.
Caution
If a referenced script is missing from your vault, the exporter finishes with a warning so you can locate or recreate the file before you share the package.
## Import a package
[Section titled “Import a package”](#import-a-package)
1. Open **Settings → QuickAdd** and click **Import package…**.
2. Paste the full contents of a `.quickadd.json` file into the text box.
3. QuickAdd analyses the JSON and shows a **review** of exactly what the package will add and run before you commit - see [Review what a package can do](#review-what-a-package-can-do).
4. Under **Choices**, pick an action for each choice:
* **Import** adds a new choice only when its ID does not already exist.
* **Overwrite** keeps the original ID and replaces the existing choice.
* **Duplicate** copies the choice with new IDs so you can keep both versions.
* **Skip** leaves the choice untouched.
5. Under **Files**, each bundled file is grouped as **Added** or **Will overwrite**. Choose **Write**, **Overwrite**, or **Skip** per file, and adjust the destination path if you want it saved elsewhere (templates default to your QuickAdd template folder when one is set). QuickAdd updates the imported choices to reference the new locations.
6. If the package runs code, tick the acknowledgement, then click **Import package**. The choices list updates immediately and a notice summarises what changed.
Note
QuickAdd rebuilds your choice hierarchy from the stored parent IDs and path hints. If it cannot find the original parent - for example, the destination vault does not contain the same multi-choice folder - the imported choice lands at the root and a warning is logged.
## Review what a package can do
[Section titled “Review what a package can do”](#review-what-a-package-can-do)
Importing a package can run scripts and macros that have full access to your vault and the network, so the import screen treats it as a trust decision: it makes everything visible **before** anything is written.
### What the package can do to your vault
[Section titled “What the package can do to your vault”](#capability-summary)
A **What this package can do** panel lists the package’s capabilities, ranked by how much they can affect your vault:
* **Runs custom JavaScript** - a user script, or a script-mode condition, that runs arbitrary code.
* **Runs on startup** - a macro set to run automatically every time Obsidian launches, with no interaction.
* **Adds commands** - choices that register a command in the palette / hotkeys.
* **Overwrites existing choices or files**, **sends content to an AI provider**, **triggers other Obsidian commands**, and similar.
Each row names the choice it comes from. Hover any badge for a plain-language explanation of what it means.
### Read the files before you trust them
[Section titled “Read the files before you trust them”](#read-the-files-before-you-trust-them)
Every bundled file appears under **Files** with its destination and size. Click **View contents** to read a script or template exactly as it will be written. Files that are run as code are marked **Executable** (regardless of their declared type), and very long or minified scripts are flagged as not fully reviewable.
A Markdown note can carry runnable code too. A bundled note whose content includes a JavaScript code fence is flagged with a critical **can be run as code** capability row and counts toward the acknowledgement gate below, because such a note executes when something uses it: a user-script step runs a note’s first `js` fence as its script, and [inline `js quickadd` fences](/docs/InlineScripts/) run whenever the note is used as a template - **including as an AI Assistant prompt template**, where the fence runs on every AI call.
### Acknowledge the code before importing
[Section titled “Acknowledge the code before importing”](#acknowledgement-gate)
When a package can run code, the **Import package** button stays disabled until you have opened **View contents** on each bundled executable script and ticked the acknowledgement. Reviewed scripts are marked so you can track what is left.
Caution
If a referenced script is **not** bundled, QuickAdd warns that it will run from whatever file already exists at that path after import.
### Preview a package from the command line
[Section titled “Preview a package from the command line”](#preview-from-the-command-line)
For scripting or CI, the `quickadd:package-preview` command returns the same review as JSON, without opening the modal:
```bash
obsidian quickadd:package-preview path=path/to/package.quickadd.json
```
Add `decode=true` to inline the decoded contents of each bundled file.
## Check version compatibility
[Section titled “Check version compatibility”](#version-compatibility)
Packages record the QuickAdd version and a schema number, so future releases can warn you when a file needs a newer plugin. If you see a schema version error, upgrade QuickAdd in both vaults and export the package again.
# Template
> Create a new note from a template file: dynamic paths and file names, a destination folder, linking, and what to do when the note already exists
A Template choice creates a **new note from a template file**. Press a hotkey, answer any prompts, and QuickAdd builds the note - filling in dates, your answers, and links as it goes. Use it to spin up a book note, a meeting note, or a project page from a layout you keep once and reuse everywhere.
Templates use QuickAdd’s own [format syntax](/docs/FormatSyntax/) - prompts, dates, and variables included - so no other plugin is required. If your templates come from the Templater plugin, see [Coming from Templater](/docs/ComingFromTemplater/) for the QuickAdd-native way to do each familiar job.

## Set up your first template choice
[Section titled “Set up your first template choice”](#set-up)
1. Create a template note, for example `Templates/Book.md`:
Templates/Book.md
```markdown
---
author:
status: reading
---
# {{VALUE:title}}
Started {{DATE}}
```
2. Open **Settings → QuickAdd**, type a name like `New book note`, choose **Template** in the dropdown, and click **Add Choice**.
3. Click the gear (⚙) next to the new choice.
4. Set **Template Path** to `Templates/Book.md`.
5. Under **New note location**, choose **In a specific folder** and enter `Books`.
6. Run it: command palette → `QuickAdd: Run`, pick `New book note`, type the title, for example `Dune`.
QuickAdd creates `Books/Dune.md` from your template, with the title, date, and frontmatter filled in. Assign the choice a hotkey (⚡ icon, or Obsidian’s Hotkeys settings) once it behaves the way you want.
## Run a template without making a choice
[Section titled “Run a template without making a choice”](#run-without-choice)
If you just want to spin up a note from a template in your [template folder](/docs/Settings/#templates--properties) without maintaining a Template choice per file, use the **New note from template** command. It lists the templates in your configured folder, prompts for the new note’s name, and creates it in Obsidian’s default location.
**New note from template** uses a discovery-first title picker: as you type the new note name, QuickAdd shows matching existing notes and unresolved wikilink targets first. Choose an existing note to open it unchanged, or choose the **Create new note** row to create the note from the selected template.
Where the command shows up
When a template folder is configured, the same entry also appears in **Run QuickAdd** - at the bottom by default, or move it to the top / hide it under [Settings → Choice picker](/docs/Settings/#choice-picker) - and it’s scriptable via [`quickadd:run-template`](/docs/Advanced/CLI/#quickaddrun-template). Make a Template choice (below) when you need a fixed location, file-name format, linking, or a hotkey.
The builder groups a Template choice’s settings into four sections: **Template** (template path and file name format), **Location** (where the file is created), **Linking** (whether and how to link to the created file), and **Behavior** (what happens when the file already exists, and how the file is opened).
## Point to the template file: Template Path
[Section titled “Point to the template file: Template Path”](#mandatory)
**Template Path** is the one required setting: the path to the template you want to insert. Paths are vault-relative; a leading `/` is ignored.
Template Path
```text
Templates/Book.md
```
QuickAdd supports markdown (`.md`), canvas (`.canvas`), and base (`.base`) templates. The created file uses the same extension as the template. If you want a new markdown note to include a live embedded Base dashboard, see [Template: Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/).
### Use a dynamic template path
[Section titled “Use a dynamic template path”](#dynamic-template-path)
The Template Path supports [format syntax](/docs/FormatSyntax/), so the path can change from run to run. Named values (`{{VALUE:client}}`), dates (`{{DATE:YYYY}}`), fields, and global variables all work in the path. The same applies to the Capture choice’s *Create file with template* path.
You configure
```text
Templates/{{VALUE:collectionName}} Template.md
```
Running the choice prompts for a collection name and resolves to a path like `Templates/Games Template.md`. The created file’s extension comes from the *resolved* path, so a placeholder that expands to `.canvas` or `.base` produces a canvas or base file.
A path is resolved with a **path-safe** subset of the format syntax:
* Macros, inline JavaScript, and `{{TEMPLATE:...}}` inclusion are **not** run while computing a path.
* `{{TITLE}}` cannot be used in a path (the title is derived from the created file, not the source template).
* The note-relative placeholders `{{FOLDER}}`, `{{FILENAMECURRENT}}`, `{{LINKCURRENT}}`, and `{{LINKSECTION}}` are left as-is in a template path, since they describe the runtime note/folder context (the target folder, or the active note and the cursor’s heading) rather than the source template. `{{FOLDER}}` is still available in file names and template bodies.
Note
A couple of things don’t apply to a *dynamic* template path: the path can’t be auto-bundled when exporting a QuickAdd package (it isn’t a literal file), and if you use the [one-page input](/docs/Advanced/onePageInputs/) form, prompts inside the resolved template’s body are gathered when the choice runs rather than in the up-front form.
## Name the new note: File Name Format
[Section titled “Name the new note: File Name Format”](#optional)
**File Name Format** sets a format for the created file’s name, using [format syntax](/docs/FormatSyntax/) - so file names can be dynamic too.
You configure
```text
£ {{DATE}} {{NAME}}
```
You get (with a typed name of Manually-Written-File-Name)
```text
£ 2021-06-12 Manually-Written-File-Name
```
`{{NAME}}` is a value you enter when invoking the template. If you **disable** File Name Format, QuickAdd uses `{{VALUE}}` as the file name format, which keeps the default behavior of prompting for a file name when you run the choice (with the same `{{VALUE}}` / `{{NAME}}` behavior described in the format syntax docs).
Note
If a value used in the file name contains a line break or another control character, QuickAdd folds it to a space in the created path and strips trailing spaces or periods from that path segment. The original value is still available unchanged in the template body, so multi-line prompts can create readable note content without making the note hard to link.
### Search for an existing note first
[Section titled “Search for an existing note first”](#search-existing)
**Search existing notes before creating** applies to Template choices that use the default note-title prompt. It opens the same discovery-first picker used by **New note from template**: matching notes and unresolved wikilink targets appear while you type, so you can open an existing note instead of creating a duplicate.
Selecting an existing note opens it unchanged and does **not** apply the template, append template content, insert links, or copy links. Selecting the explicit **Create new note** row continues with normal Template creation.
## Decide where the note is created: New note location
[Section titled “Decide where the note is created: New note location”](#new-note-location)
**New note location** is a dropdown that controls where the note is created. Pick one of four modes:
* **Obsidian default** - use Obsidian’s “Default location for new notes” setting.
* **In a specific folder** - create the note in the folder(s) you configure below. One folder creates the note there; several folders open a suggester asking which to use. An **Include subfolders** toggle (shown only in this mode) lets the suggester offer the selected folders *and* their subfolders.
* **Same folder as current file** - create the note next to the currently active file (falls back to the vault root if no file is open).
* **Ask for folder each time** - prompt you to pick any folder in the vault each time the choice runs.
Switching modes hides the fields that don’t apply, but your configured folder list is kept - switching back restores it.
Folder paths support [format syntax](/docs/FormatSyntax/), including `{{VALUE}}`, named values such as `{{VALUE:client}}`, dates, and global variables:
In a specific folder
```text
Projects/{{VALUE:client}}/{{DATE:YYYY}}
```
This prompts for a client and creates the file under that client’s folder for the current year.
## Link to the new note: Link to created file
[Section titled “Link to the new note: Link to created file”](#link-to-created-file)
**Link to created file** controls whether QuickAdd inserts a link to the note it just created - handy for leaving a trail in the note you were in. Three modes:
* **Enabled (strict)** - require the configured link destination to be available
* **Enabled (skip if unavailable)** - insert the link when possible and skip silently when a current-note destination has no focused Markdown editor
* **Disabled** - never append a link
With either enabled mode, **Link destination** controls where the link is written:
* **Current note** - insert the link into the active Markdown editor
* **Specified note** - append the link to the bottom of an existing Markdown note, such as an index or MOC, without opening that note
For **Current note**, strict mode keeps the legacy behavior and requires a focused Markdown editor. For **Specified note**, QuickAdd validates the destination note before creating the new note. It appends a normal link at the bottom of that file; it does not create the index file, insert under a heading, update properties, or remove duplicate links.
### Where the link is placed
[Section titled “Where the link is placed”](#link-placement)
For the **Current note** destination, **Link placement** chooses the spot:
* **Replace selection** - replaces any selected text with the link (default)
* **After selection** - preserves the selected text and places the link after it
* **End of line** - places the link at the end of the current line
* **New line** - places the link on a new line below the cursor
* **In frontmatter property** - adds the link to a named frontmatter property
When **In frontmatter property** is selected, set the property name and choose how strictly QuickAdd should handle missing or non-list properties:
* **Create or convert** (default) - create the property if it is missing, or convert an existing scalar value into a list before appending the new link. Object values still throw an error.
* **Create if missing** - create the property if it is missing. Existing scalar/object values still throw an error.
* **Require list** - append only to an existing list property. Empty/null properties are treated as empty lists; missing properties and existing scalar/object values throw an error.
Note
If the cursor is in an editable Obsidian Properties field when the Template choice starts, and the placement is not **In frontmatter property**, QuickAdd appends the link to that focused property instead of using the stale editor cursor behind the Properties panel. Text properties receive the link at the end of the value, and list properties receive a new list item.
### Link or embed
[Section titled “Link or embed”](#link-type)
**Link type** is shown for any **Current note** body placement (**Replace selection**, **After selection**, **End of line**, and **New line**). Choose whether QuickAdd inserts a **Link** (`[[Note]]`) or an **Embed** (`![[Note]]`). An embed transcludes the linked note’s contents at the placement position, so for example **New line** + **Embed** drops `![[Note]]` on its own line. The inline placements (**After selection**, **End of line**) insert the embed inline on the same line. The **In frontmatter property** placement and the **Specified note** destination stay link-only.
### What the link says
[Section titled “What the link says”](#link-display-text)
**Link display text** is shown for the selection placements (**Replace selection**, **After selection**) with the **Link** type. It chooses what the inserted link displays. **Selected text** keeps your highlight as the link’s display text:
You do
```text
Select "Meeting with Mark", run a Template choice whose file name format is
20240101 {{SELECTED}}
```
You get
```text
[[20240101 Meeting with Mark|Meeting with Mark]]
```
With nothing selected (or when the selection can’t be represented safely inside a link), QuickAdd inserts the plain link instead. Multi-line selections are collapsed to a single line for the display text, and vaults using Markdown-style links get `[Meeting with Mark](20240101%20Meeting%20with%20Mark.md)`.
### Copy a link to the clipboard
[Section titled “Copy a link to the clipboard”](#copy-link-to-clipboard)
**Copy link to clipboard** copies a link to the created file after the Template choice runs. This works separately from **Link to created file**, so you can copy the link without inserting it into the current note, or do both. The copied link is a vault-path wikilink, ready to paste into another note.
## Open the note after creating: Open
[Section titled “Open the note after creating: Open”](#open-created-file)
**Open** opens the created file. When enabled, additional file-opening controls appear (these are shared with the Capture choice):
* **File opening location** - where to open the file: **Reuse current tab**, **New tab**, **Split pane**, **New window**, **Left sidebar**, or **Right sidebar**.
* **Split direction** - shown only when the location is **Split pane**. Arrange the new pane as **Split right** or **Split down**.
* **View mode** - how to display the opened file: **Source**, **Preview**, **Live Preview**, or **Default**.
* **Focus new pane** - shown for every location except **Reuse current tab**. Focus the opened tab immediately after opening.
## When the note already exists
[Section titled “When the note already exists”](#file-already-exists-behavior)
**If the target file already exists** decides what QuickAdd does when a note with the target name is already there. The setting works in two steps: first pick a high-level behavior, then a follow-up field appears for the two behaviors that need a detail.
* **If the target file already exists** - choose **Ask every time**, **Update existing file**, **Create another file**, or **Keep existing file**.
* **Update action** - shown only when you choose **Update existing file**.
* **New file naming** - shown only when you choose **Create another file**.
### Let QuickAdd ask each time
[Section titled “Let QuickAdd ask each time”](#ask-every-time)
QuickAdd prompts you to choose one of these actions each time the target path already exists:
* **Append to bottom**
* **Append to top**
* **Overwrite file**
* **Increment trailing number**
* **Append duplicate suffix**
* **Do nothing**
### Update the existing note
[Section titled “Update the existing note”](#update-existing-file)
These options modify the existing markdown, canvas, or base file:
* **Append to bottom** - adds the template content to the end of the existing file.
* **Append to top** - adds the template content to the beginning of the existing file.
* **Overwrite file** - replaces the existing file content with the template.
Note
For markdown files, **Append to bottom** and **Append to top** handle template frontmatter the same way as [Apply Template to Note](/docs/ApplyTemplateToNote/): the template’s frontmatter properties are merged into the existing note instead of inserting a second `---` block. Missing or empty properties are filled from the template. If a property already contains one value, the note’s value wins. Properties that can contain multiple values add any template values that are not already present. Canvas and base files receive the template content as-is.
### Create a new note instead
[Section titled “Create a new note instead”](#create-another-file)
These options keep the existing file untouched and create a new file instead:
* **Increment trailing number** - changes trailing digits only while preserving zero padding when present. For example, `note009.md` becomes `note010.md`.
* **Append duplicate suffix** - keeps the full base name and adds `(1)`, `(2)`, and so on. For example, `note.md` becomes `note (1).md`.
### Keep the existing note
[Section titled “Keep the existing note”](#keep-existing-file)
Selecting **Keep existing file** applies the same result as choosing **Do nothing** from the prompt:
* **Do nothing** - leaves the existing file unchanged and opens it automatically. This does not require the separate **Open** setting.
# Coming from Templater
> Migrate Templater workflows to QuickAdd - templates, prompts, dates, daily notes, folder workflows, cursor placement, and scripts, done the QuickAdd-native way.
If you built your note workflows around the Templater plugin, this page maps each familiar job to the QuickAdd-native way to do it. Every pattern below runs on QuickAdd alone: one engine owns your prompts, dates, and file creation, which is what keeps you clear of double prompts and half-rendered template syntax (see [common migration snags](#common-migration-snags)).
New to QuickAdd entirely? Start with [Getting Started](/docs/) for the choice types, then come back here for the mappings.
## Point QuickAdd at your template folder
[Section titled “Point QuickAdd at your template folder”](#point-quickadd-at-your-template-folder)
You don’t need to move or rewrite your template files to start. Add your existing template folder(s) under **Template folder paths** in [Settings → Templates & properties](/docs/Settings/#templates--properties). That single step powers:
* the **QuickAdd: New note from template** command, which lists every template in those folders and prompts for the new note’s name - no per-template setup;
* the **QuickAdd: Apply template to active note** command, whose picker offers those template files too;
* template-path autocomplete when you configure choices.
Make a [Template choice](/docs/Choices/TemplateChoice/) for the templates that deserve their own hotkey, a fixed destination folder, or a file name format.
## The quick map
[Section titled “The quick map”](#the-quick-map)
The left column names the job; the middle names the Templater expression you may know it by (as a landmark only - the QuickAdd patterns don’t use or require it).
| The job | You may know it as | The QuickAdd way |
| ---------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Insert the note’s title | `tp.file.title` | [`{{TITLE}}`](/docs/FormatSyntax/#title) |
| Today’s date, any format | `tp.date.now` | [`{{DATE:YYYY-MM-DD}}`](/docs/FormatSyntax/#date-format), offsets like `{{DATE+7}}` |
| Ask for a date in plain language | `tp.system.prompt` | [`{{VDATE:due,YYYY-MM-DD}}`](/docs/FormatSyntax/#vdate) - natural language built in |
| Prompt for text | `tp.system.prompt` | [`{{VALUE:name}}`](/docs/FormatSyntax/#named-value) |
| Pick from a list | `tp.system.suggester` | [`{{VALUE:Option A,Option B}}`](/docs/FormatSyntax/#named-value), [`{{FIELD:...}}`](/docs/FormatSyntax/#field), [`{{FILE:...}}`](/docs/FormatSyntax/#file) |
| Include one template in another | `tp.file.include` | [`{{TEMPLATE:Templates/Partial.md}}`](/docs/FormatSyntax/#template) |
| Apply a template to an existing note | the insert template command | [Apply template to active note](/docs/ApplyTemplateToNote/) |
| Insert the clipboard | `tp.system.clipboard` | [`{{CLIPBOARD}}`](/docs/FormatSyntax/#clipboard) |
| Insert the selected text | `tp.selection` | [`{{SELECTED}}`](/docs/FormatSyntax/#selected) |
| Reuse a property from the note you’re in | `tp.frontmatter` | [`{{FIELD:project\|default-from:active}}`](/docs/FormatSyntax/#field-default-from-active); to re-render a value you prompted for, just [repeat `{{VALUE:name}}`](#prompt-once-reuse-everywhere) |
| Link back to the note you came from | `tp.file.path` workarounds | [`{{LINKCURRENT}}`](/docs/FormatSyntax/#linkcurrent) (a link), [`{{FILENAMECURRENT}}`](/docs/FormatSyntax/#filenamecurrent) (raw name, for embeds like `![[{{FILENAMECURRENT}}#Heading]]`), [`{{LINKSECTION}}`](/docs/FormatSyntax/#linksection) (link to the heading you’re in) |
| Run JavaScript | `tp.user` | [Inline scripts](/docs/InlineScripts/), [user scripts in macros](/docs/Choices/MacroChoice/), [`{{MACRO:...}}`](/docs/FormatSyntax/#macro) |
| Folder templates | folder templates | No automatic equivalent - see [Templates chosen by folder](#templates-chosen-by-folder) |
| Cursor marker in a template | `tp.file.cursor` | No direct equivalent - see [Where the cursor lands](#where-the-cursor-lands) |
## Create new notes from templates
[Section titled “Create new notes from templates”](#create-new-notes-from-templates)
A [Template choice](/docs/Choices/TemplateChoice/) creates a note from a template file, with a configurable destination folder, file name format, link insertion, and open behavior. QuickAdd resolves every token - prompts included - before the note is created, so the finished note is plain text from its first moment.
To position user input at an exact spot in the new note, put the token exactly where the text belongs. One named value can drive both the file name and the body:
File name format: `{{VALUE:topic}}`
```markdown
---
type: meeting
---
# {{TITLE}}
Topic: {{VALUE:topic}}
Date: {{DATE:YYYY-MM-DD}}
```
You are asked for `topic` once; the answer becomes the file name and fills the body, and `{{TITLE}}` renders the final file name.
## Add to existing notes
[Section titled “Add to existing notes”](#add-to-existing-notes)
### Shared template content in new and existing notes
[Section titled “Shared template content in new and existing notes”](#shared-template-content-in-new-and-existing-notes)
Keep one template file and use it both ways - no parallel template sets:
* **New notes**: point a Template choice at it, or include it inside a bigger template with [`{{TEMPLATE:Templates/Partial.md}}`](/docs/FormatSyntax/#template).
* **Existing notes**: run **QuickAdd: Apply template to active note** (also in a file’s right-click menu). You pick the template and where it goes - cursor, top, bottom, or replace. For the insert modes, the template’s frontmatter is merged into the note’s existing frontmatter, with the note’s own values winning (replace overwrites the whole note, frontmatter included). See [Apply Template to Note](/docs/ApplyTemplateToNote/).
A [Capture choice](/docs/Choices/CaptureChoice/) whose format is `{{TEMPLATE:Templates/Partial.md}}` also inserts that shared content into a target note. Prefer it for body-only snippets: a capture inserts the template text as-is, so a template that starts with its own `---` frontmatter block ends up as a literal second block instead of being merged. When the shared content carries frontmatter, use **Apply template to active note**.
### Today’s daily note
[Section titled “Today’s daily note”](#todays-daily-note)
Appending to today’s note is a Capture choice with a date-formatted target path - the file doesn’t have to exist beforehand:
* **Capture to**: `Daily/{{DATE}}.md`
* **Create file if it doesn’t exist**, with your daily template
* **Insert after**: `## Log`, with **Create line if not found**
* **Capture format**: `- {{VALUE}}`
Say today is 2026-07-06: running it and typing `did a thing` creates `Daily/2026-07-06.md` from the template on first capture and appends `- did a thing` under `## Log` - one hotkey, with or without an existing note. For a step-by-step walkthrough with variations, see [Capture: Add entries to your daily note](/docs/Examples/Capture_ToDailyNote/); [Capture choices](/docs/Choices/CaptureChoice/) covers every target and position option.
## Templates chosen by folder
[Section titled “Templates chosen by folder”](#templates-chosen-by-folder)
QuickAdd does not watch folders: nothing runs automatically when a note appears in a folder, no matter how it was created. What it offers instead is explicit and per-choice:
* **One Template choice per destination.** Set **New note location** to **In a specific folder** and name the choice after the destination (“New person”, “New project”). Each gets its own command and can get its own hotkey.
* **One choice, several folders.** List multiple folders on the choice (optionally **Include subfolders**) and QuickAdd asks which one at run time.
* **Dynamic paths.** Both the template path and the folder path accept [format syntax](/docs/FormatSyntax/), and one named value can drive both. A choice with template path `Templates/{{VALUE:kind}}.md` and folder path `{{VALUE:kind}}s` asks for `kind` once - answering `Person` creates the note from `Templates/Person.md` in the `Persons` folder, keeping the whole folder-to-template mapping in a single choice.
* **No setup at all**: the **New note from template** command picks any template from your template folder and creates the note in Obsidian’s default location.
## Dates without another plugin
[Section titled “Dates without another plugin”](#dates-without-another-plugin)
`{{DATE}}` renders today (`YYYY-MM-DD` by default), `{{DATE:}}` takes any [Moment format](https://momentjs.com/docs/#/displaying/format/), and offsets travel in days: `{{DATE+7}}`, `{{DATE:gggg-[W]WW+7}}`. Snap to period boundaries with [`|startof:` / `|endof:`](/docs/FormatSyntax/#date-snap), for example `{{DATE:YYYY-MM|startof:week}}` for weekly notes that file under the month the week belongs to.
For dates you enter, [`{{VDATE:name,format}}`](/docs/FormatSyntax/#vdate) understands natural language out of the box - no companion plugin. Type `tomorrow`, `next friday`, or `in two weeks`. Enter the date once, render it many times:
```markdown
Due: {{VDATE:due,YYYY-MM-DD}}
Week: {{VDATE:due,gggg-[W]WW}}
```
Typing `tomorrow` fills both lines from one prompt - with 2026-07-06 as today, that’s `Due: 2026-07-07` and `Week: 2026-W28`.
There is no token for a file’s creation or modification date - reach for a [script](#run-scripts) if you need those.
## Prompt once, reuse everywhere
[Section titled “Prompt once, reuse everywhere”](#prompt-once-reuse-everywhere)
Named values are shared across an entire run. `{{VALUE:topic}}` in the file name, the template body, and any other step of the same run all resolve from a single prompt - the mechanism behind the [Template choice example above](#create-new-notes-from-templates).
That sharing spans [Macro choice](/docs/Choices/MacroChoice/) steps too. A classic two-step flow - log a task in today’s daily note and create its note - is a macro of two choices sharing one name:
1. A Capture choice into `Daily/{{DATE}}.md` with the format `- [ ] [[{{VALUE:task}}]]`
2. A Template choice with file name format `{{VALUE:task}}` creating the note in your `Tasks` folder
You type the task name once. Scripts join the same pool: anything a script assigns to `params.variables.task` is what `{{VALUE:task}}` resolves to in later steps - see the [scripting guide](/docs/Advanced/ScriptingGuide/).
To gather every prompt on a single form up front instead of one dialog at a time, enable [one-page input](/docs/Advanced/onePageInputs/) - and see [Controlling Prompts](/docs/ControllingPrompts/) for everything about prompt order, labels, defaults, and skipping.
## Where the cursor lands
[Section titled “Where the cursor lands”](#where-the-cursor-lands)
QuickAdd has no in-template cursor marker of its own - you can’t mark an arbitrary spot in a template and land there. What you can control:
* **Captures follow the insertion.** With **Capture to active file**, the cursor ends up right after the inserted text; with **Open** enabled on other targets (opened focused, in an editing mode), QuickAdd places the cursor immediately after the inserted text.
* **Apply template to active note** offers an **Insert at cursor** mode, so content lands where you already are.
* **After creating a note**, a Template choice with **Open** doesn’t move the cursor - you typically land at the top of the note. To end at the bottom instead, wrap the Template choice in a [Macro choice](/docs/Choices/MacroChoice/) and add the **Move cursor to file end** editor command as the next step (file start and line start/end variants exist too).
## Run scripts
[Section titled “Run scripts”](#run-scripts)
QuickAdd runs JavaScript in two shapes:
* **[Inline scripts](/docs/InlineScripts/)**: a fenced code block tagged `js quickadd` inside a template body or capture format. The block runs when the choice does, and a returned string is spliced into the output in its place.
* **[User scripts](/docs/Choices/MacroChoice/)**: a `.js` file in your vault, run as a step of a Macro choice. Scripts receive `app`, the [QuickAdd API](/docs/QuickAddAPI/) (`inputPrompt`, `suggester`, `executeChoice`, and more), the shared `variables` map, and the `obsidian` module - see the [scripting guide](/docs/Advanced/ScriptingGuide/).
`{{MACRO:My macro}}` embeds a macro’s return value anywhere format syntax is accepted, so a computed value can flow straight into a file name, template body, or capture line.
## Common migration snags
[Section titled “Common migration snags”](#common-migration-snags)
These are the classic symptoms of splitting one template between two engines - each has a QuickAdd-native fix:
* **You get prompted twice.** QuickAdd resolves all of its prompts before the file is created. If another engine prompts in the same template, you answer twice - once per engine. Let QuickAdd own the prompt with `{{VALUE:name}}` and reuse the answer everywhere it’s needed.
* **Template syntax shows up unrendered.** QuickAdd renders QuickAdd tokens; another engine’s syntax is only rendered by that engine. If it isn’t installed or doesn’t run on the file, its markup stays behind as literal text. Port the line to the matching token from [the map](#the-quick-map).
* **Capturing into a note throws template errors.** A note that keeps live template syntax can re-execute or error whenever a plugin processes the file again. QuickAdd tokens like `{{DATE:YYYY-MM-DD}}` render once, at creation, into plain text - later captures find nothing to re-run. Migrate the offending line to a QuickAdd token and let QuickAdd create the note so the token renders - a Capture with **Create file if it doesn’t exist** plus that template does both (see [Today’s daily note](#todays-daily-note)).
# Controlling Prompts
> Everything you control about the questions QuickAdd asks - which ones appear, in what order, how they look, how to skip them, and which keys submit or cancel.
When a choice runs, every placeholder that needs input becomes a prompt: `{{VALUE:title}}` in a file name, `{{VDATE:due,YYYY-MM-DD}}` in a template, the target file of a Capture, and so on. This page covers everything you control about those prompts - which ones appear, the order they come in, how each one looks, how to make one skippable, the keys that submit or cancel, and the one-page form that asks everything at once.
For the full syntax of each placeholder and flag, see [Format Syntax](/docs/FormatSyntax/). For the one-page form in depth, see [One-page Inputs](/docs/Advanced/onePageInputs/).
## What QuickAdd asks you
[Section titled “What QuickAdd asks you”](#where-prompts-come-from)
QuickAdd prompts whenever it meets a placeholder it cannot fill on its own:
* [`{{VALUE}}` / `{{VALUE:name}}`](/docs/FormatSyntax/#value) ask for text, or show a pick list when you give them options.
* [`{{VDATE:name,format}}`](/docs/FormatSyntax/#vdate) asks for a date, and understands natural language like `tomorrow`.
* [`{{FIELD:name}}`](/docs/FormatSyntax/#field) suggests values a property already has in your vault.
* [`{{FILE:folder}}`](/docs/FormatSyntax/#file) asks you to pick a note from a folder.
* Template choices may also ask for a file name or folder; Capture choices may ask which file to capture to.
You are asked once per variable, per run. If `{{VALUE:title}}` appears in both the file name and the template body, you answer once and QuickAdd reuses it. A variable that already has a value never prompts: prefilled variables from a macro step, [the API](/docs/QuickAddAPI/), or the [CLI](/docs/Advanced/CLI/) skip their prompts, and even an empty string counts as an answer.
## The order prompts appear in
[Section titled “The order prompts appear in”](#prompt-order)
Prompts follow the structure of the choice, not where the placeholders sit in your text. Take this template:
```markdown
Attendees: {{VALUE:attendees}}
Due: {{VDATE:due,YYYY-MM-DD}}
```
The `due` prompt appears first, even though `attendees` comes first in the text. That is because dates are asked before named values within one piece of text. The full order:
1. **Template choices** resolve the template path first, then the folder, then the file name, and finally the template’s content. A placeholder in the file name always prompts before anything in the template body.
2. **Capture choices** resolve the capture target first, then the capture format.
3. **Within one piece of text** (a file name, a template, a capture format), prompts are grouped by kind, and only inside a kind do they follow the order they appear. The kinds run in this order: plain `{{VALUE}}`/`{{NAME}}` first, then dates (`{{VDATE}}`), then named values (`{{VALUE:name}}`), then fields (`{{FIELD}}`) and file pickers (`{{FILE}}`), with the math prompt (`{{MVALUE}}`) last.
No flag reorders individual prompts. If the sequence bothers you, switch on the [one-page input form](#one-form-instead-of-many-prompts): it lists every input in one form (still in resolution order), and you fill them in whatever order you like.
Note
A pick list defined with [`|name:`](/docs/FormatSyntax/#value-name) and its reuses can appear in any order within one piece of text. When a reuse comes before the definition, QuickAdd resolves the definition early so you are still asked only once.
## Change how a prompt looks
[Section titled “Change how a prompt looks”](#shape-an-individual-prompt)
Each control is a flag you add to the placeholder. The most common ones:
| You want | Flag | Example |
| ----------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------- |
| Helper text on the prompt | [`\|label:`](/docs/FormatSyntax/#value-label) | `{{VALUE:attendees\|label:Comma-separated names}}` |
| A pre-filled default | [`\|default:`](/docs/FormatSyntax/#value-default-option) | `{{VALUE:status\|default:open}}` |
| A large, multi-line text box | [`\|type:multiline`](/docs/FormatSyntax/#value-multiline) | `{{VALUE:notes\|type:multiline}}` |
| A number, slider, or checkbox | [`\|type:number` and friends](/docs/FormatSyntax/#value-property-types) | `{{VALUE:rating\|type:slider\|min:0\|max:10}}` |
| A pick list | [comma-separated options](/docs/FormatSyntax/#named-value) | `{{VALUE:low,medium,high}}` |
| A pick list that also accepts custom text | [`\|custom`](/docs/FormatSyntax/#value-custom) | `{{VALUE:home,work\|custom}}` |
| Several selections at once | [`\|multi`](/docs/FormatSyntax/#value-multi) | `{{VALUE:a,b,c\|multi}}` |
The full reference for every flag lives in [Format Syntax](/docs/FormatSyntax/).
Good to know:
* `|label:` works on `{{VALUE}}` placeholders and [`{{FILE:...}}`](/docs/FormatSyntax/#file) pickers, not on `{{VDATE}}` or `{{FIELD}}`. On a plain text prompt the label shows as helper text below the title; on a pick list it becomes the placeholder.
* `|type:multiline` upgrades a single placeholder to the large text box and overrides the global **Use multi-line input prompt** setting. There is no reverse flag: with the global setting on, every plain text prompt is already multi-line.
* `|type:` flags only work on single-value placeholders. A pick list ignores them.
## Make a prompt skippable
[Section titled “Make a prompt skippable”](#optional-prompts)
Add [`|optional`](/docs/FormatSyntax/#optional-fields) to let a prompt be left blank, in which case it resolves to nothing:
```markdown
- [ ] {{VALUE:task}} {{VDATE:due,[📅 ]YYYY-MM-DD|optional}}
```
An optional prompt gains a **Skip** button and treats an empty submission as the answer, so you are not asked again later in the run. `|optional` works on `{{VALUE}}` placeholders, option lists, `{{VDATE}}`, and `{{FILE}}`.
Skipping is an answer; pressing **Esc** still cancels the whole choice. If the same variable appears in several places, put `|optional` on every occurrence - that is the one spelling that behaves the same in both the sequential prompts and the one-page form.
## Keys that submit, skip, and cancel
[Section titled “Keys that submit, skip, and cancel”](#submit-keys)
| Prompt | Submit | Also useful |
| --------------------------------------------------------- | -------------------------------------------- | ------------------------------------------ |
| Single-line input (also number, slider, and date prompts) | `Enter` | |
| Multi-line input | `Ctrl/Cmd+Enter` (`Enter` inserts a newline) | `Tab` indents; `Shift+Tab` moves focus out |
| Pick list / suggester | `Enter` picks the highlighted option | |
| Math prompt ([`{{MVALUE}}`](/docs/FormatSyntax/#mvalue)) | `Ctrl/Cmd+Enter` | `Tab` jumps to the cursor marker |
| One-page input form | `Ctrl/Cmd+Enter` | `Tab` moves between fields |
| Any optional prompt | | `Ctrl/Cmd+Shift+Enter` skips |
`Esc` cancels the prompt and with it the whole run - nothing is created or captured by the cancelled choice. (In a macro, steps that already ran are not undone.) To get a notice when that happens, enable **Show input cancellation notifications** in [settings](/docs/Settings/#notifications).
## Autocomplete while you type
[Section titled “Autocomplete while you type”](#autocomplete-inside-prompts)
Inside a prompt, `#` searches your vault’s tags and `[[` searches your files (headings, blocks, and relative paths work too). See [Suggester System](/docs/SuggesterSystem/) for all triggers and keys.
These triggers work in the single-line and multi-line prompts. The one-page form’s plain text fields do not offer them, though its field and pick-list inputs have their own inline suggestions.
## One form instead of many prompts
[Section titled “One form instead of many prompts”](#one-form-instead-of-many-prompts)
Rather than answering prompts one at a time, QuickAdd can collect everything in a single form before the choice runs. Every unanswered variable appears as the right widget - text, textarea, date with a calendar, dropdown, slider - with optional fields badged, and Template choices with a file name format get a live file name preview.
* Turn it on for everything with **One-page input for choices** under [Settings → Input](/docs/Settings/#input).
* Template and Capture choices each have a **One-page input override** dropdown in their builder (**Follow global setting**, **Always**, **Never**), so you can flip the form on or off for one choice.
* A few inputs still run as follow-up steps after the form, such as [`{{FIELD:...|multi}}`](/docs/FormatSyntax/#field-multi) pickers and Capture’s insert-after heading picker.
* Cancelling the form cancels the whole run, exactly like cancelling a sequential prompt.
The full behavior - what is collected, date parsing, defaults, and script-declared inputs - is documented in [One-page Inputs](/docs/Advanced/onePageInputs/).
## For script authors
[Section titled “For script authors”](#for-script-authors)
User scripts can declare their inputs so they appear in the one-page form (`quickadd.inputs`), and can open a one-page form of their own at runtime with [`quickAddApi.requestInputs`](/docs/QuickAddAPI/). Both are covered in [One-page Inputs](/docs/Advanced/onePageInputs/#user-scripts-declare-inputs-optional).
# Examples
> Browse ready-made QuickAdd workflows for captures, templates, and macros, with setup difficulty, prerequisites, and what each creates
Use these examples when you want to copy a working pattern instead of starting from a blank choice.
| Workflow | Choice type | Setup | Prerequisites | What it creates |
| --------------------------------------------------------------------------------------------------- | ------------------ | ------------ | ---------------------------- | ------------------------------------------------------------ |
| [Capture to Your Daily Note](/docs/Examples/Capture_ToDailyNote/) | Capture | Beginner | Daily note path | Timestamped entries, tasks, quotes, callouts, and table rows |
| [Add a Task to a Kanban Board](/docs/Examples/Capture_AddTaskToKanbanBoard/) | Capture | Beginner | Obsidian Kanban plugin | A task in a board section |
| [Fetch Tasks from Todoist](/docs/Examples/Capture_FetchTasksFromTodoist/) | Capture and Macro | Intermediate | Todoist API token | Imported Todoist tasks |
| [Canvas Capture](/docs/Examples/Capture_CanvasCapture/) | Capture | Intermediate | An Obsidian Canvas file | Text added to a selected or targeted card |
| [Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/) | Template | Beginner | Inbox folder or note | A new inbox note |
| [Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/) | Template | Intermediate | Base template file | A note with an embedded Base dashboard |
| [Automatic Book Notes from Readwise](/docs/Examples/Template_AutomaticBookNotesFromReadwise/) | Template and Macro | Advanced | Readwise export script | Book notes with highlights |
| [Book Finder](/docs/Examples/Macro_BookFinder/) | Macro | Intermediate | Book lookup script | A populated book note |
| [Movie and Series Script](/docs/Examples/Macro_MovieAndSeriesScript/) | Macro | Intermediate | TMDB API key | Media notes with metadata |
| [Move Notes with a Tag](/docs/Examples/Macro_MoveNotesWithATagToAFolder/) | Macro | Intermediate | Tagged notes | Notes moved into a target folder |
| [Zettelizer](/docs/Examples/Macro_Zettelizer/) | Macro | Intermediate | Headings in an existing note | New notes split from headings |
| [Toggl Manager](/docs/Examples/Macro_TogglManager/) | Macro | Advanced | Toggl integration | Preset time entries |
## Pick by goal
[Section titled “Pick by goal”](#pick-by-goal)
### Capture information faster
[Section titled “Capture information faster”](#capture-information-faster)
Start with [Capture to Your Daily Note](/docs/Examples/Capture_ToDailyNote/) for daily-note captures. Move to [Canvas Capture](/docs/Examples/Capture_CanvasCapture/) when your target is a Canvas card instead of a Markdown note.
### Create structured notes
[Section titled “Create structured notes”](#create-structured-notes)
Start with [Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/) for a small template. Use [Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/) when you want a generated note to include a live Base dashboard.
### Run scripted workflows
[Section titled “Run scripted workflows”](#run-scripted-workflows)
Start with [Book Finder](/docs/Examples/Macro_BookFinder/) to see the common macro pattern: prompt for input, call a script, write a note, and open the result.
# Capture: Add journal entry
> Compact Capture reference for appending timestamped journal lines under a heading in your date-formatted daily note file
This pattern has a full step-by-step guide: [Capture: Add entries to your daily note](/docs/Examples/Capture_ToDailyNote/). It covers the journal-entry recipe below plus creating today’s note, inserting under a heading, tasks, quotes, callouts, table rows, and newline gotchas.
For reference, the journal entry capture in compact form:
| Setting | Value |
| -------------- | ------------------------------------------ |
| Capture to | `Daily/{{DATE:YYYY-MM-DD - ddd MMM D}}.md` |
| Write position | **After line…** |
| Insert after | `## What did I do today?` |
| Capture format | `- {{DATE:HH:mm}} {{VALUE}}\n` |
# Capture: Add a Task to a Kanban Board
> Add a task to a chosen lane on an Obsidian Kanban board by capturing after the lane heading, with optional date formatting
You end up with one QuickAdd command that drops whatever you type onto a Kanban board as a card in the lane you choose - without opening the board first.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* The [Kanban](https://github.com/mgmeyers/obsidian-kanban) community plugin, installed and enabled.
* A Kanban board with at least one lane. Each lane is a Markdown heading, for example `## Backlog`.
## Setup
[Section titled “Setup”](#setup)
1. In QuickAdd settings, add a new **Capture** choice and name it (for example, `Add to board`).
2. Open its settings.
3. Set **Capture to** to your Kanban board file.
4. Enable the **Task** toggle (in the **Content** section). This wraps your text in `- [ ]` so Kanban reads it as a card.
5. Set **Write position** to **After line…**.
6. In the **Insert after** field that appears, write `## `followed by the name of the lane you want to add the card to. For a lane called `Backlog`, that is `## Backlog`.
## What you get
[Section titled “What you get”](#what-you-get)
You run the choice, type `Buy milk`, and QuickAdd adds `- [ ] Buy milk` as a new card at the top of the `Backlog` lane.
## Add a date to the card
[Section titled “Add a date to the card”](#add-a-date-to-the-card)
Kanban recognizes a date written as `@{YYYY-MM-DD}` on a card. Enable **Capture format** and set the format to add one:
* Use today’s date automatically:
```plaintext
{{VALUE}} @{{{DATE}}}
```
* Get asked which date to use each time:
```plaintext
{{VALUE}} @{{{VDATE:DATE,gggg-MM-DD}}}
```
You can type an exact date or a natural-language date such as `tomorrow`.
Read more about [format syntax here](/docs/FormatSyntax/).

# Capture: Canvas Capture
> Capture formatted text into a selected Canvas card or a specific node in a .canvas file, with supported write positions and linking
You end up with a Capture choice that writes into an Obsidian Canvas: either the card you have selected on the board, or one specific card in a `.canvas` file you name ahead of time.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* An Obsidian Canvas file. Canvas is built into Obsidian, so there is no plugin to install.
## When to use this
[Section titled “When to use this”](#when-to-use-this)
Use Canvas capture when your workflow starts in a visual board, but you still want QuickAdd’s capture formats, variables, and hotkeys.
Good fits:
* Add a timestamped note to a selected brainstorming card.
* Append a task to a project card.
* Send repeated updates to one known Canvas card.
## Capture to the selected card
[Section titled “Capture to the selected card”](#capture-to-the-selected-card)
1. Create a Capture choice.
2. Enable **Capture to active file**.
3. Open a Canvas file.
4. Select exactly one supported card.
5. Set **Write position** to **Top of file (after frontmatter)**, **Bottom of file**, or **After line…** / **Before line…**.
6. Run the Capture choice.
Supported selected-card targets:
* Text cards
* File cards that point to Markdown files
QuickAdd aborts with a notice if no card is selected, multiple cards are selected, or the selected card is unsupported.
## Capture to a specific card
[Section titled “Capture to a specific card”](#capture-to-a-specific-card)
1. Create a Capture choice.
2. Turn off **Capture to active file**.
3. Set **Capture to** to a `.canvas` file.
4. Choose **Target canvas node**.
5. Pick the card you want QuickAdd to write to.
6. Set a supported write position.
This is the best option for repeatable workflows where every capture should go to the same Canvas card.
## Write position support
[Section titled “Write position support”](#write-position-support)
Canvas capture supports these write positions:
* **Top of file** (shown as **Top of file (after frontmatter)** when **Capture to active file** is enabled)
* **Bottom of file**
* **After line…**
* **Before line…**
Canvas capture does not support cursor-based write positions:
* **At cursor**
* **New line above cursor**
* **New line below cursor**
If **Capture to active file** is enabled and the write position is still **At cursor**, QuickAdd aborts instead of writing to the wrong place.
## Link-to-captured-file behavior
[Section titled “Link-to-captured-file behavior”](#link-to-captured-file-behavior)
When **Link to captured file** is set to **Enabled (requires active file)** and capture runs from a Canvas card without a focused Markdown editor, the capture still writes. QuickAdd skips link insertion because there is no active Markdown file to link from.
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
| Symptom | Likely cause | Fix |
| ------------------------------------------- | ------------------------------------------- | ----------------------------------------------------- |
| Capture aborts before writing | No card or multiple cards are selected | Select exactly one supported card |
| Capture aborts with cursor-position wording | The write mode is cursor-based | Use top, bottom, after-line, or before-line placement |
| Nothing is written to a file card | The file card points to a non-Markdown file | Use a Markdown file card or a text card |
| The target picker is not shown | Capture target is not a `.canvas` file | Set **Capture to** to the Canvas file path |
## Related docs
[Section titled “Related docs”](#related-docs)
* [Capture Choices](/docs/Choices/CaptureChoice/)
* [Format Syntax](/docs/FormatSyntax/)
* [Template: Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/)
# Capture: Fetch Tasks From Todoist
> Import Todoist tasks into a note using a macro and user script, selecting from all tasks, a project, or a single section
You end up with one QuickAdd command that pulls tasks from your Todoist account into a note in your vault. This is useful for capturing tasks on the go with your phone, then adding them to Obsidian when you get back to your computer.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* The [Todoist](https://github.com/jamiebrynes7/obsidian-todoist-plugin) plugin for Obsidian, set up with your API key. There is a link to grab the key in the plugin’s settings.
* A [macro](/docs/Choices/MacroChoice/) that runs the [Todoist Script](/scripts/TodoistScript.js) (set up below).
## How it works
[Section titled “How it works”](#how-it-works)
The Todoist Script has three exports, `SelectFromAllTasks`, `GetAllTasksFromProject`, and `GetAllTasksFromSection`.
* `SelectFromAllTasks` will prompt you to select tasks from all tasks on your Todoist account,
* `GetAllTasksFromProject` will prompt you for a project and get all tasks from that project, and
* `GetAllTasksFromSection` will prompt you for a section and get all tasks from that section.
Personally, I just let QuickAdd ask me which one to execute.
However, when you are entering the user script in the macro, you can add `::GetAllTasksFromProject` (or, `::` followed by any of the other exports) to directly call one of the exported functions.

**IMPORTANT:** If you do *NOT* want this script to complete tasks in Todoist that you put into your vault, remove the function call to `closeSelectedTasks`.
Now, you will need a [Capture choice](/docs/Choices/CaptureChoice/) with the following settings.
* *Capture to:* the path to the file where you want to store the tasks.
* *Capture format:* Enabled - and in the format, write `{{MACRO:}}` where `MACRONAME` is the name of the macro that you made earlier.
## What you get
[Section titled “What you get”](#what-you-get)
The tasks are written in this format: `- [ ] 📆 `
Which equals: `- [ ] Buy groceries 📆 2021-06-27`
This task will be recognized by the Tasks plugin for Obsidian, as well. If there isn’t a date set for the task, they’ll simply be entered as `- [ ] Buy groceries`.
### Steps
[Section titled “Steps”](#steps)
*NOTE:* If you simply follow the process below, you will be asked which export to execute each time. That is fine - if you want to be asked - but you can also make separate [Capture choices](/docs/Choices/CaptureChoice/) for each exported function, meaning, it’ll execute that function without asking you which one to execute. Just set up the macro as shown in the image above.
1. Set up the Todoist plugin - grab the API key from your Todoist account. There’s a link in the plugin’s settings.
2. Download the Todoist Script (linked above) and add it to your vault as a javascript file. I’d encourage you to call it something like todoistTaskSync.js to be explicit.
3. Follow along with what I do in the gif below

### Installation video
[Section titled “Installation video”](#installation-video)
# Capture: Insert a Related Notes Base into an MOC Note
> Insert a live Base view of related notes into an active MOC note by capturing from a .base template into the current file
You end up with one QuickAdd command that inserts a live “related notes” Base view into whichever map-of-content (MOC) note you have open. The table shows every note that links to that MOC and updates itself as your vault changes.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* Obsidian’s **Bases** core plugin enabled (it renders `.base` files).
* A markdown note to run the capture from, such as your MOC note.
## Why this pattern
[Section titled “Why this pattern”](#why-this-pattern)
Capture does not write directly to `.base` files, but it can still pull content from a `.base` template and insert that content into the active markdown note. This is useful for MOCs where you want a note-local index of backlinks.
## Setup
[Section titled “Setup”](#setup)
1. Create a `.base` template file, for example `Templates/MOC Related Notes.base`:
```yaml
filters:
and:
- 'file.ext == "md"'
- "file.hasLink(this.file)"
- "file.path != this.file.path"
views:
- type: table
name: Related notes
```
2. Create a Capture choice.
3. Enable **Capture to active file**.
4. Set **Write position** to **Top of file (after frontmatter)**.
5. In **Capture format**, reference your `.base` template with an explicit file extension:
Example:
````markdown
## Related Notes
```base
{{TEMPLATE:Templates/MOC Related Notes.base}}
```
Context: {{VALUE}}
````
6. Run the Capture choice while your MOC note is active (for example `MOCs/Alpha Project.md`).
QuickAdd reads the `.base` template and inserts its content into the active note. Because the base view is embedded in that note, `this.file` points at the MOC, so the table shows notes that link to that specific MOC.

# Capture: Add entries to your daily note
> Cookbook of Capture recipes that add timestamped lines, tasks, quotes, callouts, and table rows to today's daily note
This cookbook gives you one QuickAdd choice that adds text to today’s daily note - even when the note or the target heading doesn’t exist yet.
Every recipe starts from the same base Capture choice; you only change the **Capture format** and the target heading.
## Base setup
[Section titled “Base setup”](#base-setup)
1. In QuickAdd settings, add a new **Capture** choice.
2. Name it (for example, `Daily entry`) and open its settings.
3. Disable **Capture to active file**.
4. Set **Capture to** to match your vault’s daily-note path and date pattern, for example `Daily/{{DATE:YYYY-MM-DD}}.md`.
5. Enable **Create file if it doesn’t exist**.
6. Set **Write position** to **After line…**.
7. In the **Insert after** field, enter the heading you want entries placed under, for example `## Journal`.
8. Enable **Insert at end of section** so each capture appends at the bottom of the section.
9. Enable **Create line if not found** and set its placement to **Top** so the heading is inserted when a fresh note does not have it yet.
10. Leave **Link to captured file** disabled.
11. Enable **Capture format** and use one of the recipes below.
## Recipes
[Section titled “Recipes”](#recipes)
Each recipe shows what to change from the base setup.
### Timestamped journal line
[Section titled “Timestamped journal line”](#timestamped-journal-line)
Keep **Insert after** set to `## Journal`.
**Capture format:**
```plaintext
- {{DATE:HH:mm}} {{VALUE}}\n
```
Produces:
```markdown
## Journal
- 18:54 first journal entry
- 18:55 second journal entry
```
End non-task formats with `\n` so each capture lands as its own complete line.
### Task line
[Section titled “Task line”](#task-line)
Change **Insert after** to `## Tasks`.
**Task:** on (in the **Content** section).
**Capture format:**
```plaintext
{{VALUE}}
```
**Task** wraps the value in `- [ ] ...` automatically. Do not add `- [ ]` to the format manually.
### Task with a date prompt
[Section titled “Task with a date prompt”](#task-with-a-date-prompt)
**Task:** on.
**Capture format:**
```plaintext
{{VALUE}} due {{VDATE:due,YYYY-MM-DD}}
```
QuickAdd prompts for the task text and then for `due`. You can enter an exact date or a natural-language date such as `tomorrow`. Result: `- [ ] pay rent due 2026-07-07`.
### Callout line
[Section titled “Callout line”](#callout-line)
Change **Insert after** to the callout opener, for example:
```plaintext
> [!info]- Captured today
```
**Capture format:**
```plaintext
> {{VALUE}}\n
```
On first use, **Create line if not found** inserts the callout opener at the position you chose. Each subsequent capture appends before the next blank line or heading, so keep the callout as one contiguous quoted block. The `>` prefix is required to keep the entry inside the callout block.
### Quote
[Section titled “Quote”](#quote)
Change **Insert after** to `## Quotes`.
**Capture format:**
```plaintext
> {{VALUE}}\n
```
Same format as the callout recipe but targeting a regular heading. Produces a blockquote line under the section.
### Table row
[Section titled “Table row”](#table-row)
Use this when the daily note already has a table under a heading and the table is the last block in that section. Keep **Write position** as **After line…**, set **Insert after** to the heading above the table, and keep **Insert at end of section** enabled. If more content follows the table in the same section, target the table separator row instead.
**Capture format:**
```plaintext
| {{DATE:HH:mm}} | {{VALUE}} |\n
```
This keeps the row attached to the table:
```markdown
## Log
| When | What |
| --- | --- |
| 09:00 | existing |
| 18:55 | section row |
```
### Tomorrow’s daily note
[Section titled “Tomorrow’s daily note”](#tomorrows-daily-note)
Change **Capture to** to:
```plaintext
Daily/{{DATE:YYYY-MM-DD+1}}.md
```
The `+1` shifts the target date one day forward. Combine with any of the formats above.
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
**Captures run together on one line.** For non-task formats, end the capture format with `\n` or press Enter at the end of the format field. Formats that use **Task** do not need one; QuickAdd inserts that line break.
**Pasted multiple lines became one task.** The **Task** setting wraps the whole capture once. Capture one task at a time, or use an advanced macro or userscript if you need to split pasted lines into separate tasks.
**The heading is not found and capture fails.** Enable **Create line if not found** with placement **Top** (or **Bottom**). QuickAdd inserts the heading on first use and places new content after it.
**You need to insert above a placeholder.** Use **Before line…** instead of **After line…** and target the placeholder, such as ``. See [Insert before](/docs/Choices/CaptureChoice/#insert-before) for the full setting.
**Capture writes to the wrong file.** The date pattern in **Capture to** must match your vault’s daily-note naming exactly. If your notes are named `2025.01.15.md` inside `Journal/`, use `Journal/{{DATE:YYYY.MM.DD}}.md`.
**Table rows or callout content breaks when using bottom-of-file placement.** Use **After line…** with **Insert at end of section** instead of **Bottom of file**. Bottom-of-file placement starts non-task captures on a new line, and when the file already ends with a newline that leaves a blank line before the captured content. That blank line splits table rows and callout blocks.
# Macro: Add location long-lat from address
> Macro and user script that geocode an address into a location latitude-longitude property on the active note, for the Map View plugin
This macro asks you for an address, looks up its coordinates, and adds a `location` property with `[lat, long]` as its value to the note you have open. It is especially useful for the [Obsidian Map View plugin](https://github.com/esm7/obsidian-map-view), which reads that property to place notes on a map.
## Before you start
[Section titled “Before you start”](#before-you-start)
* The [MetaEdit plugin](https://github.com/chhoumann/MetaEdit) must be installed and enabled. This macro uses MetaEdit’s `createYamlProperty` function to write the property.
## Setup
[Section titled “Setup”](#setup)
1. Grab the script from [this page](/scripts/getLongLatFromAddress.js). You can either click the download link, or copy the file contents and save them as `getLongLatFromAddress.js`. The `.js` extension is essential.
2. Save the file anywhere in your vault (not inside the `.obsidian` folder). For a fuller walkthrough with video, see [the guide to installing user scripts](/docs/Examples/Capture_FetchTasksFromTodoist/).
3. In QuickAdd settings, click **Add Choice**, select **Macro**, and give it a name (I call mine `Mapper`). This creates a Macro choice and opens the Macro Builder. If you close the builder, click the gear (Configure) button on the choice in the list to reopen it. See [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough.
4. In the Macro Builder, place your cursor in the **User scripts** field to bring up a suggester, pick `getLongLatFromAddress.js` (or click **Browse** to select the file), and click **Add**. It should appear as the first command.
5. Close the QuickAdd settings.
## What you get
[Section titled “What you get”](#what-you-get)
Run the macro with the `QuickAdd: Run` command in the command palette and pick your choice. Enter an address, and QuickAdd adds a `location` property to the active note whose value is `[lat, long]` for that address.

Note
If you have MetaEdit’s edit mode set to **All Multi**, remove the braces on line 23 of the script so it reads:
```js
await createYamlProperty("location", `${lat}, ${lon}`, activeFile);
```
# Book Finder Script
> Insert book details fetched from the Google Books API into your vault using a Macro choice and template, no API key required
This macro looks up a book by title and inserts its details into a new note in your vault. It uses the Google Books API, and you don’t need an API key because it only reads publicly available information.
## Installation
[Section titled “Installation”](#installation)
This works by adding the BookFinder [user script](/docs/UserScripts/) to a [Macro choice](/docs/Choices/MacroChoice/) that you run from the main menu. You can find the script [here](/scripts/BookFinder.js).
1. Save the script (`BookFinder.js`) to your vault. Make sure it is saved as a JavaScript file, meaning that it has the `.js` at the end. **Important:** Do not save scripts in the `.obsidian` directory - they will be ignored. Valid locations include folders like `/scripts/`, `/macros/`, or any custom folder in your vault.
2. Create a new template in your designated templates folder. Example template is provided below.
3. Open the QuickAdd settings, click `Add Choice`, and select `Macro`. You decide what to name it. I named mine `Book`. This is what activates the macro.
4. Click the configure button (⚙️) on the macro choice to open the Macro Builder.
5. Add the user script to the command list.
6. Add a new Template step to the macro (the `Template` button in the command bar). This will be what creates the note in your vault. Settings are as follows:
1. Set the template path to the template you created.
2. Enable File Name Format and use `{{VALUE:fileName}}` as the file name format. You can specify this however you like. The `fileName` value is the name of the Book without illegal file name characters.
3. The remaining settings are for you to specify depending on your needs.
You can now use the macro to create notes with book information in your vault.
### Example template
[Section titled “Example template”](#example-template)
```markdown

**Author**:: {{VALUE:authors}}
**Title**:: {{VALUE:title}}
**Category**::{{VALUE:categories}}
**Status**:: 📥
**Related Books**
### Core Questions for Me
### Actions
### My Notes
## Details
{{VALUE:description}}
```
## Usage
[Section titled “Usage”](#usage)
You can pull any field from the API response into your template with a `{{VALUE:}}` placeholder (for example, `{{VALUE:title}}`). Below is an example response for the book ‘Flowers for Algernon’. The response is deeply nested, so if you want fields that the example template doesn’t already expose, you may need to extend the script to read them out.
```json
{
"kind": "books#volumes",
"totalItems": 119,
"items": [
{
"kind": "books#volume",
"id": "6P_jN6zUuMcC",
"etag": "FpDPG4koVaQ",
"selfLink": "https://www.googleapis.com/books/v1/volumes/6P_jN6zUuMcC",
"volumeInfo": {
"title": "Flowers for Algernon",
"authors": [
"Daniel Keyes"
],
"publisher": "Houghton Mifflin Harcourt",
"publishedDate": "2004",
"description": "Oscar-winning film Charly starring Cliff Robertson and Claire Bloom-a mentally challenged man receives an operation that turns him into a genius...and introduces him to heartache.",
"industryIdentifiers": [
{
"type": "ISBN_13",
"identifier": "9780156030083"
},
{
"type": "ISBN_10",
"identifier": "015603008X"
}
],
"readingModes": {
"text": false,
"image": true
},
"pageCount": 324,
"printType": "BOOK",
"categories": [
"Fiction"
],
"averageRating": 4,
"ratingsCount": 179,
"maturityRating": "NOT_MATURE",
"allowAnonLogging": true,
"contentVersion": "1.3.3.0.preview.1",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=6P_jN6zUuMcC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=6P_jN6zUuMcC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.ca/books?id=6P_jN6zUuMcC&printsec=frontcover&dq=intitle:Flowers+for+Algernon&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.ca/books?id=6P_jN6zUuMcC&dq=intitle:Flowers+for+Algernon&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/Flowers_for_Algernon.html?hl=&id=6P_jN6zUuMcC"
},
"saleInfo": {
"country": "CA",
"saleability": "NOT_FOR_SALE",
"isEbook": false
},
"accessInfo": {
"country": "CA",
"viewability": "PARTIAL",
"embeddable": true,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": false
},
"pdf": {
"isAvailable": true,
"acsTokenLink": "http://books.google.ca/books/download/Flowers_for_Algernon-sample-pdf.acsm?id=6P_jN6zUuMcC&format=pdf&output=acs4_fulfillment_token&dl_type=sample&source=gbs_api"
},
"webReaderLink": "http://play.google.com/books/reader?id=6P_jN6zUuMcC&hl=&source=gbs_api",
"accessViewStatus": "SAMPLE",
"quoteSharingAllowed": false
},
"searchInfo": {
"textSnippet": "WINNER OF THE HUGO AWARD AND THE NEBULA AWARD The classic novel that inspired the Academy Award-winning movie Charly Daniel Keyes, the author of eight books, was born in Brooklyn, New York, and received his B.A. and M.A. degrees from ..."
}
},
```
# Macro: Change properties in your daily notes (requires MetaEdit)
> Update a property in your daily note by picking it from a suggester and entering a new value, using the MetaEdit API
This macro lists every property in today’s daily journal note in a menu. Pick one, type a new value, and the macro writes it back - a quick way to update a property without opening the note or editing frontmatter by hand.
## Before you start
[Section titled “Before you start”](#before-you-start)
* The [MetaEdit plugin](https://github.com/chhoumann/MetaEdit) must be installed and enabled. This macro calls MetaEdit’s `getPropertiesInFile` and `update` functions.
## Setup
[Section titled “Setup”](#setup)
1. Save the script below to a `.js` file somewhere in your vault (not inside the `.obsidian` folder). See [the user scripts guide](/docs/UserScripts/) for how QuickAdd loads scripts.
2. In QuickAdd settings, click **Add Choice**, select **Macro**, and name it (for example, `Change property`). See [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough.
3. Click the configure button (the gear icon) to open the Macro Builder, then add your script as a **User Script** command.
4. Edit the script to point at your own daily notes:
* Change the date format from `gggg-MM-DD - ddd MMM D` to match your daily notes’ file names.
* Change the path from `bins/daily/` to wherever your daily notes live.
Run the macro, choose a property from the menu, and enter its new value.
If you already know which properties you want to change and don’t want to be asked about the rest, replace the suggester’s property list with a plain array of property names. You’d pass that array to the `suggester` method instead.
```js
module.exports = async (params) => {
const {quickAddApi: {inputPrompt, suggester}} = params;
const {update, getPropertiesInFile} = app.plugins.plugins["metaedit"].api;
const date = window.moment().format("gggg-MM-DD - ddd MMM D");
const dailyJournalFilePath = `bins/daily/${date}.md`;
const propertiesInDailyJournal = await getPropertiesInFile(dailyJournalFilePath);
const targetProp = await suggester(propertiesInDailyJournal.map(p => p.key), propertiesInDailyJournal);
const newPropertyValue = await inputPrompt(`Log ${targetProp.key}`, targetProp.content, targetProp.content);
await update(targetProp.key, newPropertyValue, dailyJournalFilePath);
}
```
# Macro: Log book to daily journal
> Log the book you are reading to your daily journal note's Book property with an input prompt and the MetaEdit API
This macro asks which book you are reading and writes your answer to the **Book** property of today’s daily journal note, so you can log your current read without leaving the command palette.
## Before you start
[Section titled “Before you start”](#before-you-start)
* The [MetaEdit plugin](https://github.com/chhoumann/MetaEdit) must be installed and enabled. This macro calls MetaEdit’s `update` function to change the property.
* A daily journal note whose path matches the one in the script. This example uses `bins/daily/{date}.md`; change the folder and the date format on the marked lines to match your own daily notes.
## Setup
[Section titled “Setup”](#setup)
1. Save the script below to a `.js` file somewhere in your vault (not inside the `.obsidian` folder). See [the user scripts guide](/docs/UserScripts/) for how QuickAdd loads scripts.
2. In QuickAdd settings, click **Add Choice**, select **Macro**, and name it (for example, `Log Book`). See [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough.
3. Click the configure button (the gear icon) to open the Macro Builder, then add your script as a **User Script** command.
Run the macro and enter a book title at the prompt. QuickAdd updates the **Book** property in today’s journal note to that title.

```js
// You have to export the function you wish to run.
// QuickAdd automatically passes a parameter, which is an object with the Obsidian app object
// and the QuickAdd API (see description further on this page).
module.exports = async (params) => {
// Object destructuring. We pull inputPrompt out of the QuickAdd API in params.
const {
quickAddApi: { inputPrompt },
} = params;
// Here, I pull in the update function from the MetaEdit API.
const { update } = app.plugins.plugins["metaedit"].api;
// This opens a prompt with the header "📖 Book Name". val will be whatever you enter.
const val = await inputPrompt("📖 Book Name");
// This gets the current date in the specified format.
const date = window.moment().format("gggg-MM-DD - ddd MMM D");
// Invoke the MetaEdit update function on the Book property in my daily journal note.
// It updates the value of Book to the value entered (val).
await update("Book", val, `bins/daily/${date}.md`);
};
```
# Migrate Dataview Properties to Frontmatter
> Migrate inline Dataview properties to YAML frontmatter with wikilink-aware comma handling and selective property lists
This script allows you to migrate inline Dataview properties to YAML frontmatter. It’s particularly useful when transitioning from Dataview’s inline syntax to native Obsidian properties, which offer better performance and broader compatibility.
The script handles comma-separated values with special care for commas inside wikilinks, ensuring that links like `[[Note, with comma]]` are preserved correctly.
## Use Case
[Section titled “Use Case”](#use-case)
If you’ve been using Dataview’s inline property syntax like this:
```markdown
Reference:: [[2025-10-12 - Sun Oct 12]]
Related:: [[Agentic Engineering|Agentic coding]], [[Note, with comma]]
Tags:: #project, #important
```
This script will migrate those properties to frontmatter:
```markdown
---
Reference: "[[2025-10-12 - Sun Oct 12]]"
Related:
- "[[Agentic Engineering|Agentic coding]]"
- "[[Note, with comma]]"
tags:
- "project"
- "important"
---
```
The inline properties are removed from the body of the note after migration.
**Note:** The `tags` property is automatically normalized to lowercase and `#` symbols are stripped, following Obsidian’s frontmatter conventions for reserved property names.
## Installation
[Section titled “Installation”](#installation)
1. Save the script (`migrateDataviewToFrontmatter.js`) to your vault. Make sure it is saved as a JavaScript file, meaning that it has the `.js` at the end. **Important:** Do not save scripts in the `.obsidian` directory - they will be ignored. Valid locations include folders like `/scripts/`, `/macros/`, or any custom folder in your vault.
2. Open the QuickAdd settings, click “Add Choice”, and select “Macro”. Give it a name - I named mine `Migrate Properties`.
3. Click the configure button (⚙) on the macro choice to open the Macro Builder.
4. Add the user script to the macro’s command list.
5. Click the cog ⚙ icon next to the script command to configure its settings (see Configuration below).
You can download the script here: [migrateDataviewToFrontmatter.js](/scripts/migrateDataviewToFrontmatter.js)
## Configuration
[Section titled “Configuration”](#configuration)
The script offers two configuration options:
### Migrate All Properties
[Section titled “Migrate All Properties”](#migrate-all-properties)
**Type:** Toggle (on/off) **Default:** Off
When enabled, the script will migrate **all** inline properties found in the note, regardless of their names. When disabled, only the properties specified in “Properties to Migrate” will be migrated.
### Properties to Migrate
[Section titled “Properties to Migrate”](#properties-to-migrate)
**Type:** Text input **Default:** `Reference, Related`
A comma-separated list of property names to migrate (case-insensitive). This setting is ignored if “Migrate All Properties” is enabled.
**Examples:**
* `Reference, Related` - Migrates only Reference and Related properties
* `Author, Title, Date, Tags` - Migrates these four properties
* Leave empty with “Migrate All Properties” off to use the default (Reference, Related)
## Usage
[Section titled “Usage”](#usage)
1. Open a note that contains inline Dataview properties
2. Run the macro (via command palette, QuickAdd menu, or hotkey)
3. The script will:
* Add the properties to the note’s frontmatter
* Remove the inline property lines from the note body
* Show a notification with the migrated property names
## Key Features
[Section titled “Key Features”](#key-features)
* **Smart Wikilink Parsing**: Preserves commas inside wikilinks (e.g., `[[Note, with comma]]`)
* **Comma-Separated Values**: Automatically handles multiple values separated by commas
* **Single vs Multiple Values**: Stores single values as strings, multiple values as arrays
* **Case-Insensitive Matching**: Property names are matched case-insensitively
* **Selective Migration**: Choose to migrate all properties or only specific ones
* **Reserved Property Handling**: Special handling for Obsidian reserved properties like `tags`
* **Frontmatter Merging**: Merges with existing frontmatter values instead of overwriting (deduplicates)
* **Clean Output**: Removes excessive blank lines after migration
## Examples
[Section titled “Examples”](#examples)
### Example 1: Migrate Specific Properties
[Section titled “Example 1: Migrate Specific Properties”](#example-1-migrate-specific-properties)
**Configuration:**
* Migrate All Properties: Off
* Properties to Migrate: `Reference, Related`
**Input:**
```markdown
# My Note
Reference:: [[2025-10-12 - Sun Oct 12]]
Related:: [[Agentic Engineering|Agentic coding]]
Author:: John Doe
Status:: In Progress
```
**Output:**
```markdown
---
Reference: "[[2025-10-12 - Sun Oct 12]]"
Related: "[[Agentic Engineering|Agentic coding]]"
---
# My Note
Author:: John Doe
Status:: In Progress
```
Note: Only Reference and Related were migrated. Author and Status remain as inline properties.
### Example 2: Migrate All Properties
[Section titled “Example 2: Migrate All Properties”](#example-2-migrate-all-properties)
**Configuration:**
* Migrate All Properties: On
* Properties to Migrate: (ignored)
**Input:**
```markdown
# Project Notes
Reference:: [[Main Document]]
Related:: [[Doc A]], [[Doc B]]
Author:: John Doe
Status:: In Progress
Priority:: High
```
**Output:**
```markdown
---
Reference: "[[Main Document]]"
Related:
- "[[Doc A]]"
- "[[Doc B]]"
Author: "John Doe"
Status: "In Progress"
Priority: "High"
---
# Project Notes
```
All inline properties are migrated to frontmatter.
### Example 3: Handling Complex Wikilinks
[Section titled “Example 3: Handling Complex Wikilinks”](#example-3-handling-complex-wikilinks)
**Input:**
```markdown
Related:: [[Book Title, by Author]], [[Article, from Journal]], [[Simple Note]]
```
**Output:**
```markdown
---
Related:
- "[[Book Title, by Author]]"
- "[[Article, from Journal]]"
- "[[Simple Note]]"
---
```
Commas inside wikilinks are preserved correctly.
### Example 4: Handling Tags Property
[Section titled “Example 4: Handling Tags Property”](#example-4-handling-tags-property)
**Input:**
```markdown
Tags:: #project, #work, #important
```
**Output:**
```markdown
---
tags:
- "project"
- "work"
- "important"
---
```
The `tags` property is special in Obsidian:
* It’s automatically normalized to lowercase (even if you write `Tags::` or `TAGS::`)
* The `#` symbols are stripped from tag values
* This follows Obsidian’s frontmatter convention where tags don’t use `#`
### Example 5: Merging with Existing Frontmatter
[Section titled “Example 5: Merging with Existing Frontmatter”](#example-5-merging-with-existing-frontmatter)
**Input:**
```markdown
---
tags:
- existing-tag
Reference: "[[Existing Reference]]"
---
# My Note
Tags:: #new-tag, #another-tag
Reference:: [[New Reference]]
Related:: [[Some Link]]
```
**Output:**
```markdown
---
tags:
- existing-tag
- new-tag
- another-tag
Reference:
- "[[Existing Reference]]"
- "[[New Reference]]"
Related: "[[Some Link]]"
---
# My Note
```
The script **merges** values instead of overwriting:
* Existing `tags` are preserved and new tags are added
* Existing `Reference` value is kept and the new one is added
* `Related` is added as a new property
* All values are deduplicated
## Obsidian Reserved Properties
[Section titled “Obsidian Reserved Properties”](#obsidian-reserved-properties)
Obsidian has special handling for certain property names in frontmatter. The script automatically handles these:
### tags
[Section titled “tags”](#tags)
* **Must be lowercase**: `Tags::` is converted to `tags:` in frontmatter
* **No hash symbols**: `#project` becomes `project`
* **Why**: Obsidian’s native tag system expects tags without `#` in frontmatter
**Example:**
```markdown
# Inline format (with #)
Tags:: #project, #important
# Frontmatter format (without #)
---
tags:
- project
- important
---
```
Other reserved properties (like `aliases`, `cssclass`) are preserved as-is but may have special behaviors in Obsidian. Always verify the [Obsidian documentation](https://help.obsidian.md/Editing+and+formatting/Properties) for the latest reserved property names.
## Technical Details
[Section titled “Technical Details”](#technical-details)
### Smart Comma Splitting
[Section titled “Smart Comma Splitting”](#smart-comma-splitting)
The script uses a custom parser that tracks whether it’s inside a wikilink when splitting comma-separated values:
```javascript
function parseCommaSeparatedWithWikilinks(value) {
let insideWikilink = false;
for (let i = 0; i < value.length; i++) {
if (char === '[' && nextChar === '[') {
insideWikilink = true;
}
if (char === ']' && prevChar === ']') {
insideWikilink = false;
}
// Only split on commas outside wikilinks
if (char === ',' && !insideWikilink) {
// Split here
}
}
}
```
### How the write works (and a concurrency caveat)
[Section titled “How the write works (and a concurrency caveat)”](#how-the-write-works-and-a-concurrency-caveat)
The script updates the frontmatter with `processFrontMatter`, then strips the migrated inline properties from the body inside an `app.vault.process` callback. Using `process` for the body rewrite (rather than a separate `read` + `modify`) means the body’s read-and-rewrite happens in one callback, so it does not clobber an unrelated concurrent edit elsewhere in the body:
```javascript
// Update the frontmatter first
await app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
// Add properties to frontmatter
});
// Then strip the migrated inline properties from the body
await app.vault.process(activeFile, (data) => {
const { cleanedContent } = parseInlineFieldsWithWikilinks(data, propertiesToMigrate, migrateAll);
return cleanedContent;
});
```
These are still two separate steps. The frontmatter is written from the file as it was first read, while the body is re-scanned at rewrite time. If another device or window adds a *new* inline field to the same note in the brief window between the two steps, that field can be removed from the body without being captured in frontmatter. This is why the Tips above matter: avoid running a bulk migration on a note that is being edited or synced at the same time, and keep a backup.
## Tips
[Section titled “Tips”](#tips)
1. **Test on a Copy First**: Try the script on a copy of your note to ensure it works as expected
2. **Use Version Control**: If you use Git, commit your vault before running bulk migrations
3. **Migrate Gradually**: Start with specific properties before using “Migrate All”
4. **Check Frontmatter Format**: After migration, verify that the frontmatter is formatted correctly
5. **Tags Property**: Remember that `tags` must be lowercase in frontmatter and shouldn’t have `#` symbols
6. **Combine with Other Macros**: You can add additional steps to the macro, such as opening a specific note or running another script
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
**Properties not migrating:**
* Check that the property names are spelled correctly in the settings
* Ensure the inline properties use `::` syntax (two colons)
* Verify that the properties aren’t inside fenced/inline code or task checkboxes (these are intentionally skipped - see “Code blocks” below)
* Inline-field-shaped lines inside fenced code blocks (` ``` ` or `~~~`) and inline code spans (`` `...` ``) are detected and left untouched, so they are never migrated or removed. This holds for fences at the top level, inside blockquotes, and nested in a list item (for example a ` ``` ` fence whose opener sits on the same line as a `-` bullet).
* 4-space *indented* code blocks are **not** detected. A line shaped like `name:: value` inside an indented code block can still be migrated and removed from the body. Distinguishing a genuine indented code block from ordinary indented list or paragraph text requires block context that a single-pass line scanner does not have, and a partial heuristic would either delete real indented code or silently skip a genuine inline field that is merely indented under a list (which Dataview *does* treat as a field). The script therefore favors migrating real fields over silently skipping them - so use a fenced code block (` ``` `) rather than 4-space indentation for any code you want to protect.
* As a general safety net, prefer migrating a specific property list over “Migrate All Properties”, and follow the Tips above - test on a copy and keep your vault under version control before a bulk migration.
**Commas not handled correctly:**
* Make sure wikilinks use proper `[[` and `]]` syntax
* Check for unmatched brackets in your wikilinks
**How are existing frontmatter values handled:**
* The script merges with existing frontmatter values instead of overwriting
* If a property already exists in frontmatter, the new values are combined and deduplicated
* Example: If frontmatter has `tags: [project]` and inline has `Tags:: #work`, result will be `tags: [project, work]`
## Related Resources
[Section titled “Related Resources”](#related-resources)
* [QuickAdd API Reference](/docs/QuickAddAPI/)
* [User Scripts Documentation](/docs/UserScripts/)
* [processFrontMatter API](https://docs.obsidian.md/Reference/TypeScript+API/FileManager/processFrontMatter)
# Macro: Move notes with a tag to a folder
> Move every note carrying a chosen tag to a target folder, matching both frontmatter and inline tags, via a macro script
This macro moves every note carrying a tag you pick into a folder you pick. It matches the tag whether it lives in a note’s frontmatter or inline in the body, and it can optionally include nested tags (for example `#project/work` when you choose `#project`). No extra plugins are needed - it uses only Obsidian’s own API.

## Setup
[Section titled “Setup”](#setup)
1. Save the script below to a `.js` file somewhere in your vault (not inside the `.obsidian` folder). See [the user scripts guide](/docs/UserScripts/) for how QuickAdd loads scripts.
2. In QuickAdd settings, click **Add Choice**, select **Macro**, and name it (for example, `Move tagged notes`). See [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough.
3. Click the configure button (the gear icon) to open the Macro Builder, then add your script as a **User Script** command.
Run the macro, pick a tag, choose whether to include nested tags, then pick the destination folder. Every matching note moves there.
```js
module.exports = async function moveFilesWithTag(params) {
const {
app,
quickAddApi: { suggester, yesNoPrompt },
} = params;
const allTags = Object.keys(app.metadataCache.getTags());
const tag = await suggester(allTags, allTags);
if (!tag) return;
const shouldMoveNested = await yesNoPrompt(
"Should I move nested tags, too?",
`If you say no, I'll only move tags that are strictly equal to what you've chosen. If you say yes, I'll move tags that are nested under ${tag}.`
);
const cache = app.metadataCache.getCachedFiles();
let filesToMove = [];
// Helper function to get tags as array from frontmatter
// Handles both string format ("tag1 tag2") and array format (["tag1", "tag2"])
function getTagsAsArray(tagValue) {
if (!tagValue) return [];
if (Array.isArray(tagValue)) return tagValue;
if (typeof tagValue === 'string') return tagValue.split(" ");
return [];
}
cache.forEach((key) => {
if (key.contains("template")) return;
const fileCache = app.metadataCache.getCache(key);
// Check if file has the tag we're looking for
let hasMatchingTag = false;
const cleanTag = tag.replace("#", "");
// Check frontmatter tags (supports tags, Tags, tag, Tag)
if (fileCache.frontmatter) {
const tagFields = ['tags', 'Tags', 'tag', 'Tag'];
for (const field of tagFields) {
const tagsArray = getTagsAsArray(fileCache.frontmatter[field]);
if (!shouldMoveNested) {
// Exact match
if (tagsArray.some(t => t === cleanTag)) {
hasMatchingTag = true;
break;
}
} else {
// Nested match (contains)
if (tagsArray.some(t => t.includes(cleanTag))) {
hasMatchingTag = true;
break;
}
}
}
}
// Check inline tags (#tag in the note content)
if (!hasMatchingTag && fileCache.tags) {
if (!shouldMoveNested) {
hasMatchingTag = fileCache.tags.some(t => t.tag === tag);
} else {
hasMatchingTag = fileCache.tags.some(t => t.tag.includes(tag));
}
}
if (hasMatchingTag) filesToMove.push(key);
});
const folders = app.vault
.getAllLoadedFiles()
.filter((f) => f.children)
.map((f) => f.path);
const targetFolder = await suggester(folders, folders);
if (!targetFolder) return;
for (const file of filesToMove) {
const tfile = app.vault.getAbstractFileByPath(file);
await app.fileManager.renameFile(
tfile,
`${targetFolder}/${tfile.name}`
);
}
};
```
# Movie & Series Script
> Insert a movie or TV show note from the OMDb API into your vault with a Macro choice and template, requires an API key
This script allows you to easily insert a movie or TV show note into your vault.
We use OMDb api to get the movie or TV show information. You can get an API key on the website [here](https://www.omdbapi.com/). This will be needed to use this script.
## Demo
[Section titled “Demo”](#demo)

## Installation
[Section titled “Installation”](#installation)
Prerequisites
* You must have an OMDb API key. Request one at `https://www.omdbapi.com/` and keep it handy. The script will not run without it.
This works by adding the movies [user script](/docs/UserScripts/) to a [Macro choice](/docs/Choices/MacroChoice/) that you run from the main menu. I have made a video which shows you how to do so - [click here](https://www.youtube.com/watch?v=gYK3VDQsZJo\&t=1730s). You can find the script [here](/scripts/movies.js).
1. Save the script (`movies.js`) to your vault. Make sure it is saved as a JavaScript file, meaning that it has the `.js` at the end. **Important:** Do not save scripts in the `.obsidian` directory - they will be ignored. Valid locations include folders like `/scripts/`, `/macros/`, or any custom folder in your vault.
2. Create a new template in your designated templates folder. Example template is provided below.
3. Open the QuickAdd settings and click “Add Choice”. Select “Macro” and give it a name - you decide what to call it. I named mine `🎬 Movie`. This is what activates the macro.
4. Click the configure button (⚙️) on your new Macro choice to open the Macro Builder.
5. Add the user script to the command list.
6. Add a Template command to the macro. This will be what creates the note in your vault. Settings are as follows:
1. Set the template path to the template you created.
2. Enable File Name Format and use `{{VALUE:fileName}}` as the file name format. You can specify this however you like. The `fileName` value is the name of the Movie or TV show without illegal file name characters.
3. The remaining settings are for you to specify depending on your needs.
7. Click on the cog icon to the right of the script command to configure the script settings. This should allow you to enter the API key you got from OMDb. [Image demonstration](../Images/moviescript_settings.jpg).
You can now use the macro to create notes with movie or TV show information in your vault.
### Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
* “TypeError: Failed to construct ‘URL’: Invalid URL”
* Ensure you are using the latest `movies.js` from this repo. The example has been updated to avoid `new URL()` internally.
* Verify your OMDb API key is entered in the script settings (cog icon on the script step).
* “No results found.”
* Try searching by the exact IMDb ID (e.g. `tt0111161`).
* Check for typos in the title or try a more specific query.
* Confirm your OMDb API key is valid and not rate limited.
### Example template
[Section titled “Example template”](#example-template)
This template stores the movie’s metadata as Obsidian **front matter properties**. The multi-value fields (`cast`, `genre`, `director`) come from the script as lists and become proper **List** properties with clickable links. Scalar link/text values are wrapped in quotes so the front matter stays valid, and the plot lives in the note body so longer text (with punctuation or quotes) can’t break the front matter.
```markdown
---
category: "{{VALUE:typeLink}}"
director: {{VALUE:directorLink}}
genre: {{VALUE:genreLinks}}
cast: {{VALUE:actorLinks}}
year: "{{VALUE:Year}}"
imdbId: "{{VALUE:imdbID}}"
imdb: "[IMDb]({{VALUE:imdbUrl}})"
ratingImdb: "{{VALUE:imdbRating}}"
rating:
cover: "{{VALUE:Poster}}"
---

{{VALUE:Plot}}
```
Tip
Keep the quotes around single-link and text values (for example `category` and `imdbId`). A bare `[[Movies]]` in front matter is read by Obsidian as a nested list rather than a link. The list fields (`cast`, `genre`, `director`) don’t need quotes - QuickAdd writes them as real list properties for you.
## Usage
[Section titled “Usage”](#usage)
It’s possible to access whichever JSON variables are sent in response through a `{{VALUE:}}` tag (e.g. `{{VALUE:Title}}`). Below is an example response for the TV show ‘Arcane’.
```json
{
"Title": "Arcane",
"Year": "2021–",
"Rated": "TV-14",
"Released": "06 Nov 2021",
"Runtime": "N/A",
"Genre": "Animation, Action, Adventure",
"Director": "N/A",
"Writer": "N/A",
"Actors": "Hailee Steinfeld, Kevin Alejandro, Jason Spisak",
"Plot": "Set in utopian Piltover and the oppressed underground of Zaun, the story follows the origins of two iconic League champions-and the power that will tear them apart.",
"Language": "English",
"Country": "United States, France",
"Awards": "N/A",
"Poster": "https://m.media-amazon.com/images/M/MV5BYmU5OWM5ZTAtNjUzOC00NmUyLTgyOWMtMjlkNjdlMDAzMzU1XkEyXkFqcGdeQXVyMDM2NDM2MQ@@._V1_SX300.jpg",
"Ratings": [
{
"Source": "Internet Movie Database",
"Value": "9.2/10"
}
],
"Metascore": "N/A",
"imdbRating": "9.2",
"imdbVotes": "105,113",
"imdbID": "tt11126994",
"Type": "series",
"totalSeasons": "2",
"Response": "True"
}
```
# Toggl Manager
> Start preset Toggl Track time entries from a customizable menu using a macro and the Obsidian Toggl integration plugin
This [Macro](/docs/Choices/MacroChoice/) allows you to set preset time entries for [Toggl Track](https://track.toggl.com).
It uses the [Toggl plugin](https://github.com/mcndt/obsidian-toggl-integration) for [Obsidian](https://obsidian.md). Make sure that is set up before you continue.

We’ll need to install a QuickAdd user script for this to work. I have made a video which shows you how to do so - [click here](https://www.youtube.com/watch?v=gYK3VDQsZJo\&t=1730s). You will need to put the user script into a new macro and then create a Macro choice in the main menu to activate it. You can find the script [here](/scripts/togglManager.js).
## Installation
[Section titled “Installation”](#installation)
1. Save the script (`togglManager.js`) to your vault. Make sure it is saved as a JavaScript file, meaning that it has the `.js` at the end. **Important:** Do not save scripts in the `.obsidian` directory - they will be ignored. Valid locations include folders like `/scripts/`, `/macros/`, or any custom folder in your vault.
2. Open the QuickAdd plugin settings, click “Add Choice”, and select “Macro”. You decide what to name it. I named mine `⏳ Toggl Manager`. This is what activates the macro.
3. Click the configure button (the cog ⚙ icon) on the choice to open the Macro Builder.
4. Add the user script to the command list.
Your Macro should look like this:

Your Macro Choice should look like this:

## Configuration
[Section titled “Configuration”](#configuration)
You will need to configure your script to match your own settings. I have included some example settings from my own setup, but you’ll likely want to make it match your own preferences.
To customize the script, open the JavaScript file you just saved. You’ll see this menu setup:
```js
const menu = {
"🧠 Learning & Skill Development": { // Sub-menu for Learning and Skill Development
togglProjectName: "Learning & Skill Development", // Name of your corresponding Toggl project
menuOptions: {
"✍ Note Making": "Note Making", // Preset time entry. The left part is what's displayed, and the right part is what Toggl gets.
"🃏 Spaced Repetition": "Spaced Repetition", // So for this one, I would see '🃏 Spaced Repetition' in my menu, but Toggl would receive 'Spaced Repetition' as the entry.
"📖 Read Later Processing": "Read Later Processing",
"👨💻 Computer Science & Software Engineering": "Computer Science & Software Engineering",
}
},
"🤴 Personal": {
togglProjectName: "Personal",
menuOptions: {
"🏋️♂️ Exercise": "Exercise",
"🧹 Chores": "Chores",
"👨🔬 Systems Work": "Systems Work",
"🌀 Weekly Review": "Weekly Review",
"📆 Monthly Review": "Monthly Review",
"✔ Planning": "Planning",
}
},
"👨🎓 School": {
togglProjectName: "School",
menuOptions: {
"🧠 Machine Intelligence (MI)": "Machine Intelligence (MI)",
"💾 Database Systems (DBS)": "Database Systems (DBS)",
"🏃♂ Agile Software Engineering (ASE)": "Agile Software Engineering (ASE)",
"💻 P5": "P5",
}
}
};
```
In the menu, there’ll be 3 sub-menus with their own time entries. I have added some comments to explain the anatomy of the menu.
You can customize it however you like. You can add more menus, remove menus, and so on.
# Zettelizer
> Create a linked note from each heading of a chosen level in your active file, naming it after the heading text
This macro turns the headings of your active note into separate linked notes. For each heading of the level you choose, it creates a new note named after the heading text and links back to that heading - a fast way to break a big note into atomic, connected notes.

## Setup
[Section titled “Setup”](#setup)
Get the `.js` file for this user script [here](/scripts/zettelizer.js), then add it to a Macro choice. To install it, follow the same process as in the [fetch tasks from Todoist example - with video](/docs/Examples/Capture_FetchTasksFromTodoist/), and see [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough of creating a macro.
Next, define the folder you want the script to place the new notes in.
This can be done on line 19, where it says `const folder = "..."`. Change the text inside the `""` to match the desired folder path.
Currently, the script *only* looks for level 3 headers. This means headers with three pound symbols, like so `### header`.
You can freely change this. On line 29 it says `if (heading.level === 3)`. You can change this to any other number, denoting the heading level desired. You can also, rather than checking for equality (`===`), check for other conditions, such as `heading.level >= 1`, which denotes headers of level 1 or greater.
The script looks for headers in your active file with the desired level. If such a header is found, it will ignore the first ‘word’ (any sequence of characters - i.e., letters, numbers, symbols, etc - followed by a space). Then, it will create a file with a name containing the remaining text in the heading.
In that file, it will link to the heading it created the file from.
# Template: Add an Inbox Item
> Create a timestamped inbox note from a template, naming the file with the current date and time plus your input
This example gives you a QuickAdd choice that creates a new inbox note in one step. QuickAdd asks you for a short name, then makes a note whose file name is the current date and time followed by what you typed - so every capture lands in your inbox as its own dated note, ready to process later.
## Before you start
[Section titled “Before you start”](#before-you-start)
* A template note you want each inbox item to start from. This example uses `bins/templates/Inbox Template.md`. The template can be as simple as an empty file or contain any [format placeholders](/docs/FormatSyntax/) you like.
## Setup
[Section titled “Setup”](#setup)
1. In QuickAdd settings, click **Add Choice**, select **Template**, and give it a name (for example, `Inbox Item`).
2. Click the configure button (the gear icon) on the choice to open the Template choice settings. For a full tour of these settings, see [the Template choice docs](/docs/Choices/TemplateChoice/).
3. Set **Template Path** to your inbox template:
```plaintext
bins/templates/Inbox Template.md
```
4. Enable **File Name Format** and set it to:
```plaintext
{{DATE:YYYY-MM-DD-HH-mm-ss}} {{NAME}}
```
`{{DATE:YYYY-MM-DD-HH-mm-ss}}` becomes the current date and time down to the second, and `{{NAME}}` becomes whatever you type when the choice runs. Together they keep every inbox note uniquely named and in date order.
5. Choose the folder new notes should go into and set the remaining options to your liking.

## What you get
[Section titled “What you get”](#what-you-get)
Run the choice and type a short name, such as `call dentist`. QuickAdd creates a note named like `2026-07-08-14-30-05 call dentist.md` from your inbox template.
# Template - My Book Notes template
> Pull a book's highlights from Readwise into a new note using a Template choice and a bundled highlight-fetching macro
This example creates a new book note from a template and fills in a book’s highlights straight from [Readwise](https://readwise.io). When you run it, you pick a book, and QuickAdd builds a note whose body already contains that book’s highlights and notes.

## Before you start
[Section titled “Before you start”](#before-you-start)
* A Readwise account and its access token. Get your token [here](https://readwise.io/access_token).
* The sample template’s title and date lines (`<% ... %>`) are [Templater](https://github.com/SilentVoid13/Templater) syntax, so you need the Templater plugin for those to render. Every QuickAdd placeholder uses `{{ ... }}` instead.
## Installation
[Section titled “Installation”](#installation)
Here’s a video guide for [installing user scripts in QuickAdd](/docs/Examples/Capture_FetchTasksFromTodoist/#installation-video).
1. Create a new JavaScript file (with the `.js` extension) containing the [script below](#script). In it, replace `YOUR_READWISE_TOKEN` with your own Readwise token.
2. Create the macro that runs the script: open QuickAdd’s settings, click **New choice**, and select **Macro**. Name it (I use `Readwise`), then click the configure button (the gear icon) to open the Macro Builder. See [the Macro choice docs](/docs/Choices/MacroChoice/) for a full walkthrough.
3. In the builder, add a **User Script** command: type the name of the script you created (or click **Browse**) and click **Add**.
4. Create a [Template choice](/docs/Choices/TemplateChoice/) whose **Template Path** points at the template you made from the [one below](#template). Set the remaining options to your liking. The screenshot shows settings resembling mine:

A few notes on how it behaves:
* The note is named after the book you select. I prepend a `{ `to that name, because I use it to denote literature notes in my vault.
* Running the choice opens a menu to choose a book, and the highlights are appended into the template where the macro placeholder sits.
* Customize the template however you like, but keep `{{MACRO:Readwise::instaFetchBook}}` - that placeholder is what fetches the highlights and marks where they are inserted. If you named your macro something other than `Readwise`, replace `Readwise` in that placeholder with your macro’s name.
## Script
[Section titled “Script”](#script)
Most of the setup is shown in the gif.
```js
module.exports = { start, getDailyQuote, instaFetchBook, getBooks };
const apiUrl = "https://readwise.io/api/v2/";
const books = "📚 Books",
articles = "📰 Articles",
tweets = "🐤 Tweets",
supplementals = "💭 Supplementals",
podcasts = "🎙 Podcasts",
searchAll = "🔍 Search All Highlights (slow!)";
const categories = {
books,
articles,
tweets,
supplementals,
podcasts,
searchAll,
};
const randomNumberInRange = (max) => Math.floor(Math.random() * max);
const token = "YOUR_READWISE_TOKEN";
let quickAddApi;
async function start(params) {
({ quickAddApi } = params);
let highlights;
const category = await categoryPromptHandler();
if (!category) return;
if (category === "searchAll") {
highlights = await getAllHighlights();
} else {
let res = await getHighlightsByCategory(category);
if (!res) return;
const { results } = res;
const item = await quickAddApi.suggester(
results.map((item) => item.title),
results
);
if (!item) return;
params.variables["author"] = `[[${item.author}]]`;
const res2 = await getHighlightsForElement(item);
if (!res2) return;
highlights = res2.results.reverse();
}
const textToAppend = await highlightsPromptHandler(highlights);
return !textToAppend ? "" : textToAppend;
}
async function getBooks(params) {
const { results: books } = await getHighlightsByCategory("books");
const bookNames = books.map((book) => book.title);
const selectedBook = await params.quickAddApi.suggester(
bookNames,
bookNames
);
params.variables["Book Title"] = selectedBook;
return selectedBook;
}
async function instaFetchBook(params) {
const bookTitle = params.variables["Book Title"];
if (!bookTitle) return await start(params);
const { results: books } = await getHighlightsByCategory("books");
const book = books.find((b) =>
b.title.toLowerCase().contains(bookTitle.toLowerCase())
);
if (!book) throw new Error("Book " + bookTitle + " not found.");
params.variables["author"] = `[[${book.author}]]`;
const highlights = (await getHighlightsForElement(book)).results.reverse();
return writeAllHandler(highlights);
}
async function getDailyQuote(params) {
const category = "supplementals";
const res = await getHighlightsByCategory(category);
if (!res) return;
const { results } = res;
const targetItem = results[randomNumberInRange(results.length)];
const { results: highlights } = await getHighlightsForElement(targetItem);
if (!highlights) return;
const randomHighlight = highlights[randomNumberInRange(highlights.length)];
const quote = formatDailyQuote(randomHighlight.text, targetItem);
return `${quote}`;
}
async function categoryPromptHandler() {
const choice = await quickAddApi.suggester(
Object.values(categories),
Object.keys(categories)
);
if (!choice) return null;
return choice;
}
async function highlightsPromptHandler(highlights) {
const writeAll = "Write all highlights to page",
writeOne = "Write one highlight to page";
const choices = [writeAll, writeOne];
const choice = await quickAddApi.suggester(choices, choices);
if (!choice) return null;
if (choice == writeAll) return writeAllHandler(highlights);
else return await writeOneHandler(highlights);
}
function writeAllHandler(highlights) {
return highlights
.map((hl) => {
if (hl.text == "No title") return;
const { quote, note } = textFormatter(hl.text, hl.note);
return `${quote}${note}`;
})
.join("\n\n");
}
async function writeOneHandler(highlights) {
const chosenHighlight = await quickAddApi.suggester(
highlights.map((hl) => hl.text),
highlights
);
if (!chosenHighlight) return null;
const { quote, note } = textFormatter(
chosenHighlight.text,
chosenHighlight.note
);
return `${quote}${note}`;
}
function formatDailyQuote(sourceText, sourceItem) {
let quote = sourceText
.split("\n")
.filter((line) => line != "")
.map((line) => {
return `> ${line}`;
});
const attr = `\n>\\- ${sourceItem.author}, _${sourceItem.title}_`;
return `${quote}${attr}`;
}
function textFormatter(sourceText, rawSourceNote) {
// Readwise can return a highlight without a note (null/undefined rather
// than ""); normalize once so the .includes probe below can't throw and
// abort the whole import on a single note-less highlight.
const sourceNote = rawSourceNote ?? "";
let quote = sourceText
.split("\n")
.filter((line) => line != "")
.map((line) => {
if (sourceNote.includes(".h1")) return `## ${line}`;
else return `> ${line}`;
})
.join("\n");
let note;
if (sourceNote.includes(".h1") || sourceNote == "" || !sourceNote) {
note = "";
} else {
note = "\n\n" + sourceNote;
}
return { quote, note };
}
async function getHighlightsByCategory(category) {
return apiGet(`${apiUrl}books`, { category, page_size: 1000 });
}
async function getHighlightsForElement(element) {
return apiGet(`${apiUrl}highlights`, {
book_id: element.id,
page_size: 1000,
});
}
async function getAllHighlights() {
const MAX_PAGE_SIZE = 1000;
const URL = `${apiUrl}highlights`;
let promises = [];
const { count } = await apiGet(URL);
const requestsToMake = Math.ceil(count / MAX_PAGE_SIZE);
for (let i = 1; i <= requestsToMake; i++) {
promises.push(apiGet(URL, { page_size: MAX_PAGE_SIZE, page: i }));
}
const allHighlights = (await Promise.all(promises)).map((hl) => hl.results);
return allHighlights;
}
async function apiGet(url, data) {
let finalURL = new URL(url);
if (data)
Object.keys(data).forEach((key) =>
finalURL.searchParams.append(key, data[key])
);
return await fetch(finalURL, {
method: "GET",
cache: "no-cache",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${token}`,
},
}).then(async (res) => await res.json());
}
```
## Template
[Section titled “Template”](#template)
```md
---
image:
tags: in/books
aliases:
- <% tp.file.title.replace('{ ', '') %>
cssclass:
---
# Title: [[<%tp.file.title%>]]
## Metadata
Tags::
Type:: [[{]]
Author:: {{VALUE:author}}
Reference::
Rating::
Reviewed Date:: [[<%tp.date.now("gggg-MM-DD - ddd MMM D")%>]]
Finished Year:: [[<%tp.date.now("gggg")%>]]
# Thoughts
# Actions Taken / Changes
# Summary of Key Points
# Highlights & Notes
{{MACRO:Readwise::instaFetchBook}}
```
# Template: Create an MOC Note with a Link Dashboard
> Create a map-of-content note with an embedded Base dashboard showing its backlinks and outgoing links, via a Template choice
Use this pattern when you want QuickAdd to create a new map-of-content note that already contains a live Base dashboard for both backlinks and outgoing links.
Note
This example uses Obsidian’s [Bases](https://help.obsidian.md/bases) core plugin to render the dashboard, so make sure Bases is enabled in **Settings > Core plugins** before you begin.
## Why this pattern
[Section titled “Why this pattern”](#why-this-pattern)
A Template choice can create the note and insert the dashboard in one step. The note stays markdown, while the embedded `.base` block gives you a live view of how that note connects to the rest of your vault.
This works well for maps of knowledge, hub notes, topic notes, and evergreen indexes.
## Setup
[Section titled “Setup”](#setup)
1. Create a reusable `.base` template, for example `Templates/MOC Link Dashboard.base`:
```yaml
formulas:
note_link: "file.asLink()"
properties:
formula.note_link:
displayName: Note
file.folder:
displayName: Folder
file.mtime:
displayName: Updated
views:
- type: table
name: Backlinks
filters:
and:
- 'file.ext == "md"'
- "file.hasLink(this.file)"
- "file.path != this.file.path"
order:
- formula.note_link
- file.folder
- file.mtime
- type: table
name: Outgoing links
filters:
and:
- 'file.ext == "md"'
- "this.file.hasLink(file)"
- "file.path != this.file.path"
order:
- formula.note_link
- file.folder
- file.mtime
```
2. Create a markdown template, for example `Templates/MOC Link Dashboard.md`:
````markdown
---
tags:
- moc
---
# {{VALUE:moc_title}}
## Link Dashboard
Use the view picker in this embedded base to switch between backlinks and
outgoing links for this note.
```base
{{TEMPLATE:Templates/MOC Link Dashboard.base}}
```
## Notes
- Start linking this note to related ideas.
````
3. Create a **Template** choice (see [the Template choice docs](/docs/Choices/TemplateChoice/)) with settings like these:
* **Template Path**: `Templates/MOC Link Dashboard.md`
* **File Name Format**: `{{VALUE:moc_title}}`
* **Create in folder**: your MOC folder, for example `MOCs`
* **Open**: enabled
* **If the target file already exists**: `Create another file`
* **New file naming**: `Increment trailing number`
4. Run the Template choice and enter a title such as `Alpha Project`.
## What you get
[Section titled “What you get”](#what-you-get)
QuickAdd creates a new markdown note with an embedded Base block. Inside the note:
* `Backlinks` shows notes that link to the new MOC.
* `Outgoing links` shows notes the MOC links to.
* Both views use `this.file`, so the dashboard automatically scopes itself to the note that was just created.
After the note exists, add links in either direction and the dashboard updates with the current graph around that note.

# FAQ
> How to sync your QuickAdd choices, macros, and scripts between devices, and why API keys stay local to each one
## Syncing QuickAdd between devices
[Section titled “Syncing QuickAdd between devices”](#syncing-quickadd-between-devices)
QuickAdd keeps everything - your choices, macros, global variables, and settings - in `/.obsidian/plugins/quickadd/data.json`. As long as your sync setup includes Obsidian’s configuration folder, that one file brings your whole QuickAdd setup to the other device.
Two things don’t travel with it:
* **The plugin itself.** QuickAdd must be installed and enabled on the other device. Obsidian tracks enabled plugins in `/.obsidian/community-plugins.json`.
* **Secrets.** API keys stored through Obsidian’s secret storage stay on the device where you entered them, so enter them once per device.
One gotcha catches almost everyone: QuickAdd reads `data.json` when it loads, so a device that’s already running keeps the choices it loaded earlier. After the files have synced, restart Obsidian on the receiving device - or toggle QuickAdd off and on in **Settings -> Community plugins** - and your changes appear.
If you use **Obsidian Sync**, also check **Settings -> Sync** on each device:
* Enable **Active community plugin list** and **Installed community plugin list** under **Vault configuration sync** if you want Obsidian Sync to install and enable QuickAdd for you.
* If a macro runs a standalone `.js` user script, enable **Sync all other types**. Obsidian Sync skips `.js` files unless that setting is on (see [Sync settings](https://obsidian.md/help/sync/settings)) - which is why a macro can arrive on the other device with its script missing: the configuration syncs, the script doesn’t, and the macro fails.
* Scripts kept in Markdown notes sync like any other note and sidestep the file-type toggle entirely. See [User Scripts](/docs/UserScripts/) for both script forms.
With iCloud, Dropbox, Git, Syncthing, or any other file-sync tool, the file-type toggle doesn’t apply - just make sure the tool syncs the whole `.obsidian` folder plus your script files, then restart or re-enable QuickAdd on the other device.
For a one-time transfer, [export a QuickAdd package](/docs/Choices/Packages/) and import it on the other device. A package moves your QuickAdd configuration and bundled dependent scripts. Secrets still stay local, so enter those on each device.
# Format syntax
> Placeholders like {{DATE}} and {{VALUE}} that QuickAdd replaces with real values: dates, your answers, links, clipboard content, and more
Format syntax lets you put **placeholders** in anything QuickAdd creates. When a choice runs, each placeholder is replaced with a real value: today’s date, an answer you type, a link to the note you came from.
You can use placeholders anywhere QuickAdd asks for a format: file name fields, capture formats, folder paths, “Insert after” targets, and inside template files.
For example, a Capture with this format:
```markdown
- {{DATE:HH:mm}} {{VALUE}}
```
asks you for a value, and if you answer `Standup moved to Wednesday`, it inserts:
```markdown
- 09:42 Standup moved to Wednesday
```
You describe the shape once; QuickAdd fills in the blanks every run.
## Quick reference
[Section titled “Quick reference”](#quick-reference)
**Ask for input**
| Placeholder | What it does |
| -------------------------------------------- | ------------------------------------------------------- |
| [`{{VALUE}}`](#value) | Ask for text |
| [`{{VALUE:title}}`](#named-value) | Ask for text once, reuse the answer anywhere as `title` |
| [`{{VALUE:Red,Green,Blue}}`](#value-suggest) | Pick from a list |
| [`{{VDATE:due,YYYY-MM-DD}}`](#vdate) | Ask for a date (“tomorrow” works) |
| [`{{FIELD:project}}`](#field) | Suggest values that property already has in your vault |
| [`{{FILE:People}}`](#file) | Pick a note from a folder |
| [`{{MVALUE}}`](#mvalue) | Write a math formula (LaTeX) |
**Dates**
| Placeholder | What you get |
| ---------------------------------------------- | ------------------------------------------- |
| [`{{DATE}}`](#date) | Today, like `2026-07-08` |
| [`{{DATE:MMMM Do}}`](#date-format) | Today, formatted your way: `July 8th` |
| [`{{DATE+7}}`](#date) | Seven days from today |
| [`{{DATE:YYYY-MM\|startof:week}}`](#date-snap) | The week’s starting month, for weekly notes |
| [`{{TIME}}`](#time) | The current time, like `14:05` |
**The note you ran QuickAdd from**
| Placeholder | What you get |
| ----------------------------------------- | --------------------------------------- |
| [`{{LINKCURRENT}}`](#linkcurrent) | A link to it: `[[That note]]` |
| [`{{LINKSECTION}}`](#linksection) | A link to the section your cursor is in |
| [`{{FILENAMECURRENT}}`](#filenamecurrent) | Its file name |
| [`{{FOLDERCURRENT}}`](#foldercurrent) | Its folder |
| [`{{SELECTED}}`](#selected) | The text you had selected |
**The note being created**
| Placeholder | What you get |
| ----------------------- | -------------------------------- |
| [`{{TITLE}}`](#title) | The new note’s file name |
| [`{{FOLDER}}`](#folder) | The folder the new note lands in |
**Other content**
| Placeholder | What it inserts |
| ------------------------------------------------ | --------------------------------- |
| [`{{CLIPBOARD}}`](#clipboard) | Whatever you copied last |
| [`{{TEMPLATE:Templates/Meeting.md}}`](#template) | The contents of a template file |
| [`{{MACRO:My Macro}}`](#macro) | Whatever a macro returns |
| [`{{GLOBAL_VAR:Header}}`](#global-var) | A snippet you defined in settings |
| [`{{RANDOM:6}}`](#random) | A random ID like `x7k2p9` |
## Dates
[Section titled “Dates”](#dates)
### Today’s date: `{{DATE}}`
[Section titled “Today’s date: {{DATE}}”](#date)
`{{DATE}}` becomes today’s date in `YYYY-MM-DD` format.
Add `+N` to move the date: `{{DATE+3}}` is three days from now, `{{DATE+-3}}` is three days ago.
You write
```markdown
Daily/{{DATE}}.md
Review on {{DATE+7}}
```
You get (on July 8th, 2026)
```markdown
Daily/2026-07-08.md
Review on 2026-07-15
```
### Choose the date format: `{{DATE:}}`
[Section titled “Choose the date format: {{DATE:\}}”](#date-format)
Put a [Moment.js format](https://momentjs.com/docs/#/displaying/format/) after the colon to control how the date looks. The `+N` day offset works here too: `{{DATE:YYYY-MM-DD+3}}`.
| You write | You get |
| --------------------------- | ------------------ |
| `{{DATE:MMMM Do, YYYY}}` | `July 8th, 2026` |
| `{{DATE:YYYY-MM-DD_HH-mm}}` | `2026-07-08_09-42` |
| `{{DATE:[Week] w}}` | `Week 28` |
Tip
Literal text inside a date format goes in square brackets, like `[Week]` above. Otherwise Moment.js treats every letter as a date token.
### Snap to the start or end of a week, month, or year
[Section titled “Snap to the start or end of a week, month, or year”](#date-snap)
Add `|startof:` or `|endof:` to move the date to the boundary of a period before formatting. `` is one of `year`, `quarter`, `month`, `week`, `isoweek`, or `day` (case-insensitive).
This matters when the formatted output should reflect the period rather than the exact day. The month of a week-snapped date is the month the *week* starts in, not today’s calendar month.
| You write (on Thursday 2023-06-01) | You get |
| -------------------------------------- | ------------------------- |
| `{{DATE:gggg.MM.[Wk]w\|startof:week}}` | `2023.05.Wk22` |
| `{{DATE:YYYY-MM\|startof:month}}` | `2023-06` |
| `{{DATE:YYYY-MM-DD\|endof:month}}` | `2023-06-30` |
| `{{DATE:YYYY-[Q]Q\|startof:quarter}}` | `2023-Q2` |
| `{{DATE:GGGG-[W]WW\|startof:isoweek}}` | ISO week, Monday-anchored |
* `week` starts on your locale’s first day of the week (matching the `w`/`ww`/`gggg` tokens).
* `isoweek` starts on Monday (matching the `W`/`WW`/`GGGG` tokens).
**Example: weekly notes that cross months.** A weekly note named `gggg.MM.[Wk]w` should file the week of June 1 under May (`2023.05.Wk22`), while the heading inside the note still shows the actual day. Snap only the file name:
```markdown
File name: {{DATE:gggg.MM.[Wk]w|startof:week}}
In the note: {{DATE:M.DD dddd}}
```
Snapping also works on [`{{VDATE}}`](#vdate), so one picked date can be week-snapped in the file name and day-accurate in the body: `{{VDATE:d,gggg.MM.[Wk]w|startof:week}}` and `{{VDATE:d,M.DD dddd}}` share the same prompt. Combine freely with `|default`, `|optional`, and `|time` in any order.
Good to know:
* The `+N` day offset is applied **before** the snap, so `{{DATE:YYYY-MM-DD+7|startof:week}}` means “the start of next week”.
* `endof:` snaps to the last moment of the period (`23:59:59.999`), so `{{DATE:YYYY-MM-DD HH:mm|endof:day}}` renders `... 23:59`.
* `|startof:` and `|endof:` are the only special pipe options in a date format. Any other literal `|` is kept as-is: `{{DATE:YYYY|MM}}` gives `2023|06`.
* An unknown unit (like `|startof:fortnight`) shows an error listing the valid units.
*Introduced in QuickAdd 2.14.0.*
### The current time: `{{TIME}}`
[Section titled “The current time: {{TIME}}”](#time)
`{{TIME}}` becomes the current time in `HH:mm` format. `{{TIME:}}` takes any [Moment.js format](https://momentjs.com/docs/#/displaying/format), the same as `{{DATE:}}`.
You write
```markdown
- {{TIME}} {{VALUE}}
Meeting {{DATE}} {{TIME:HH.mm}}
```
You get
```markdown
- 14:05 Standup moved to Wednesday
Meeting 2026-07-08 14.05
```
Use `{{TIME:HH.mm}}` rather than `{{TIME}}` inside a file name: `:` is not allowed in file names on Windows or macOS.
Unlike `{{DATE}}`, `{{TIME}}` takes no `+N` offset. For a time other than “now”, use `{{DATE:HH:mm}}` with an offset, or ask for one with [`{{VDATE:, |time}}`](#vdate).
### Ask for a date: `{{VDATE:, }}`
[Section titled “Ask for a date: {{VDATE:\, \}}”](#vdate)
`{{VDATE:due,YYYY-MM-DD}}` opens a date prompt and inserts your answer in the given format. You can type natural language: `today`, `in two weeks`, `next monday`. Short aliases like `t` (today), `tm` (tomorrow), and `yd` (yesterday) work too, and are configurable in settings.
The name (`due` above) makes it a variable: enter the date once, use it in as many places and formats as you like.
You write
```markdown
Due: {{VDATE:due,YYYY-MM-DD}}
Week: {{VDATE:due,gggg-[W]WW}}
```
You get (after answering "friday")
```markdown
Due: 2026-07-10
Week: 2026-W28
```
Note
Pipes (`|`) can’t be part of a VDATE date format; everything after the first pipe is read as the default value and flags. For a literal separator, use bracketed text instead: `{{VDATE:due,[Due ]YYYY-MM-DD}}`.
#### Give the date prompt a default
[Section titled “Give the date prompt a default”](#vdate-default)
Add `|` after the format. If you submit the prompt empty, the default is used. Defaults can be natural language too: `{{VDATE:date,YYYY-MM-DD|today}}`, `{{VDATE:due,YYYY-MM-DD|next monday}}`, `{{VDATE:d,YYYY-MM-DD|+7 days}}`. The short aliases (`t`, `tm`, `yd`) also work as defaults.
A default combines with the [`optional` flag](#optional-fields) in any order: `{{VDATE:due,YYYY-MM-DD|tomorrow|optional}}` and `{{VDATE:due,YYYY-MM-DD|optional|tomorrow}}` are equivalent.
#### Ask for a time too: `|time`
[Section titled “Ask for a time too: |time”](#vdate-time)
Add `|time` (aliases: `|datetime`, `|type:datetime`) to put a time picker on the date prompt - made for `Date & time` properties. The calendar gains an `HH:mm` control, and picking a day keeps the time you set. If you omit the date format, it defaults to `YYYY-MM-DD HH:mm`.
```markdown
---
start: {{VDATE:start,YYYY-MM-DDTHH:mm|time}}
---
```
Combines with a default and `optional` in any order: `{{VDATE:meeting,YYYY-MM-DD HH:mm|tomorrow at 3pm|time|optional}}`. Without `|time`, the picker stays date-only.
*Introduced in QuickAdd 2.14.0.*
## Ask for input
[Section titled “Ask for input”](#ask-for-input)
### Ask for text: `{{VALUE}}`
[Section titled “Ask for text: {{VALUE}}”](#value)
`{{VALUE}}` opens a prompt and inserts whatever you type. `{{NAME}}` is the same thing under another name.
You write (capture format)
```markdown
- [ ] {{VALUE|label:Task}}
```
You get (after typing "Buy milk")
```markdown
- [ ] Buy milk
```
If text is selected in the editor when the choice runs, the selection is used as the value instead of prompting. For Capture choices you can turn selection-as-value off, globally or per capture.
Paste images straight into the prompt
Prompts whose answer lands in note content accept images. Paste (Ctrl/Cmd+V) a screenshot or copied image: QuickAdd saves it using Obsidian’s attachment settings and inserts an embedded link at the cursor. You can mix typed text and images, and paste more than one. Clipboard text wins over an image when both are present (copying a file in a file manager usually pastes its path as text). Prompts for file names, folders, capture targets, and insert-after/before targets never accept image paste, since an embed link would break the path. Pasted attachments are ordinary vault files; cancelling the prompt afterwards does not delete them.
Good to know:
* **In macros**, `{{VALUE}}` / `{{NAME}}` ask again for each template step. Use a [named value](#named-value) like `{{VALUE:sharedName}}` when one answer should be reused across the whole macro.
* **In `js quickadd` blocks**, don’t put `{{VALUE}}` inside JavaScript string literals. Use the QuickAdd API (`this.quickAddApi.inputPrompt(...)`) and `this.variables` instead. See [Inline scripts](/docs/InlineScripts/#execution-order-and-value).
* **From the API**, pass the value programmatically under the reserved variable name `value`.
### Name the answer so you can reuse it: `{{VALUE:}}`
[Section titled “Name the answer so you can reuse it: {{VALUE:\}}”](#named-value)
Give the value a name and QuickAdd asks once, then inserts the same answer at every other place the name appears - even across the steps of a macro.
You write (template)
```markdown
---
title: {{VALUE:title}}
---
# {{VALUE:title}}
```
You get (after answering "Project kickoff")
```markdown
---
title: Project kickoff
---
# Project kickoff
```
You can create as many named values as you need.
### Offer a list to pick from: `{{VALUE: