Install
$ agentstack add skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
About
Meta Bind — buttons, inputs, view fields
This skill covers the Meta Bind Obsidian plugin (id obsidian-meta-bind-plugin, by Moritz Jung). Authoring rules, full schemas, JS Engine integration, and the validation traps that cost hours when not known up front.
Docs root: .
When to use this skill
Reach for this skill on any of:
- User mentions Meta Bind,
INPUT[...],VIEW[...],BUTTON[...],`meta-bind-button,`meta-bindcode blocks. - User wants to add an interactive widget into a note (toggle, slider, button, dropdown, date picker, etc.).
- User reports
META_BIND_ERROR,[MB_BUTTON],Button ID not Found, "validation failed", "Invalid input at 'style'", or any Meta Bind error overlay. - User wants to bind metadata across files (
file^property,memory^...,globalMemory^...). - User needs to wire a button to an Obsidian command, JS code, file open, metadata mutation, or templater.
- User authors a tracker or dashboard with declarative INPUT fields tied to frontmatter.
Prerequisites
Confirm before debugging:
- Meta Bind plugin installed and enabled. Check
.obsidian/plugins/obsidian-meta-bind-plugin/manifest.json. - JS Engine plugin installed and enabled if any button uses
action.type: inlineJSorjs. Without it, JS actions silently fail. - Settings flag
enableJs: truein.obsidian/plugins/obsidian-meta-bind-plugin/data.json. JS-based buttons and JS view fields don't run otherwise. excludedFoldersin the samedata.json: any path matching an entry is skipped by Meta Bind. Default containstemplates. If a tracker file lives underTemplates/, Meta Bind ignores it. Move the file or remove the exclusion.
Button schema — every field
Source: .
| Field | Type | Required | Notes | |---|---|---|---| | label | string | yes | Display text on the button. | | style | enum | yes | One of default, primary, destructive, plain. Validation fails if missing or out of enum — error reads Invalid input at 'style'. | | icon | string | no | Lucide icon name. | | class | string | no | Space-separated CSS classes. | | cssStyle | string | no | Inline style rules (CSS text). | | backgroundImage | string | no | URL or vault path. | | tooltip | string | no | Hover text. Defaults to label when unset. | | id | string | no | Used for BUTTON[id] inline references in the same note. | | hidden | boolean | no | When true the code-block declaration is not rendered itself; only BUTTON[id] references render the button. | | action | object | no | Single action; mutually exclusive with actions. | | actions | object[] | no | Array of actions executed in sequence; mutually exclusive with action. |
*One of action or actions is required for the button to do anything.
Minimal working button
```meta-bind-button label: Run Script style: primary action: type: inlineJS code: console.log('clicked')
### Inline reference + hidden declaration
```markdown
Click here: `BUTTON[my-button-id]`
```meta-bind-button
label: Do thing
id: my-button-id
style: primary
hidden: true
action:
type: command
command: app:reload
The declaration is hidden; the inline `BUTTON[my-button-id]` renders the actual button at the cursor position. If `BUTTON[x]` shows `Button ID not Found`, the declaration with `id: x` either doesn't exist in the same file or **failed validation** and was dropped from the index — open the error overlay to see why.
## Button actions — types and schemas
Source: . Action types confirmed in the plugin source: `command`, `open`, `input`, `sleep`, `inlineJS`, `js`, `templaterCreateNote`, plus metadata actions.
### `command` — run an Obsidian command
```yaml
action:
type: command
command: obsidian-meta-bind-plugin:open-faq
Find command IDs via the Meta Bind helper command "Select and copy command id".
open — open a file or URL
action:
type: open
link: "[[Some Note]]" # or "https://example.com"
newTab: false # optional
inlineJS — run JS code embedded in YAML
Requires JS Engine plugin + enableJs: true in Meta Bind settings.
action:
type: inlineJS
code: |
const file = app.workspace.getActiveFile();
new Notice("hi from " + file.basename);
Inside the code, two extras are available: context.buttonConfig (the button's own config, read-only) and context.buttonContext (button metadata). Standard Obsidian globals — app, Notice, window.moment, etc. — all work.
js — run a JS file from the vault
action:
type: js
file: scripts/myAction.js
The JS file lives in the vault. Same JS Engine + enableJs requirement.
sleep — pause between actions (only useful with actions:)
action:
type: sleep
ms: 500
input — type text at the cursor (in the editor)
action:
type: input
str: "some text"
insertIntoNote — insert text or Templater output at a line
action:
type: insertIntoNote
line: "selfEnd + 1" # absolute number or relative expression
value: "Hello, world!" # plain text — or a Templater template path
templater: false # set true if value is a template path
Relative-line expressions: selfStart, selfEnd, selfStart - 1, selfEnd + 1 (relative to the button's own position). Absolute number also accepted.
replaceInNote — replace a line range
action:
type: replaceInNote
fromLine: 3
toLine: 5
replacement: "## New section\n\nfresh content"
templater: false # true → replacement is a template path
templaterCreateNote — new note from a Templater template
action:
type: templaterCreateNote
templateFile: "Templates/Lecture.md"
folderPath: "Lectures" # optional, default = vault root
fileName: "New Lecture - RENAME ME" # optional
openNote: true # optional
openIfAlreadyExists: false # optional — opens existing instead of duplicate
createNote — new empty note
action:
type: createNote
fileName: "New Note"
folderPath: "My Folder" # optional
openNote: false # optional
openIfAlreadyExists: false # optional — appends running number on conflict
Other action types (consult docs when needed)
setMetadata/updateMetadata— write to frontmatter at a bind target.regexp— regex search-and-replace within the host note.runTemplaterFile— execute a Templater template as a script (no new note).
Reference URL pattern: /reference/buttonactions//.
Multiple actions in one button
actions:
- type: command
command: theme:use-light
- type: sleep
ms: 1000
- type: command
command: theme:use-dark
Input fields — schema
Inline syntax: ` INPUT[type(args):bindTarget] . Arguments go inside type(...). The bind target after :` is optional but strongly recommended — without it, the input loses state on note reload.
Full type list:
| Type | Inline form | Common arguments | |---|---|---| | toggle | INPUT[toggle:done] | onValue(), offValue() | | slider | INPUT[slider(minValue(0), maxValue(10)):rating] | minValue(), maxValue(), stepSize() | | text | INPUT[text(placeholder('name')):name] | placeholder(), defaultValue() | | textArea | INPUT[textArea:notes] | placeholder() | | number | INPUT[number(minValue(0)):count] | minValue(), maxValue() | | date | INPUT[date:due] | — | | time | INPUT[time:meet] | — | | datePicker | INPUT[datePicker:deadline] | — | | select | INPUT[select(option(a), option(b)):choice] | option() | | multiSelect | INPUT[multiSelect(option(x), option(y)):tags] | option() | | inlineSelect | INPUT[inlineSelect(option(yes), option(no)):flag] | option() | | suggester | INPUT[suggester(option(A), option(B)):val] | option() | | editor | INPUT[editor:body] | — | | progressBar | INPUT[progressBar(minValue(0), maxValue(100)):pct] | minValue(), maxValue() | | list | INPUT[list:items] | — | | listSuggester | INPUT[listSuggester(option(x)):items] | option() | | imageSuggester | INPUT[imageSuggester:img] | — | | inlineList | INPUT[inlineList:keywords] | — | | inlineListSuggester | INPUT[inlineListSuggester(option(t)):tags] | option() |
option() two-arg form option(value, displayName) distinguishes stored value from rendered label.
To author a more complex input field, use a fenced code block:
````markdown
```meta-bind INPUT[select( option(low, "Low priority"), option(med, "Medium"), option(high, "High priority"), class(my-css-class) ):priority]
````
### Input field arguments — full list
Arguments go inside `INPUT[type(arg1, arg2, ...):bindTarget]`. Multiple comma-separated.
| Argument | Applies to | Form | Example |
|---|---|---|---|
| `option(value)` / `option(value, displayName)` | select, multiSelect, suggester, listSuggester, inlineSelect, inlineListSuggester, imageSuggester | Two-form: with one arg, value = displayName. With two args, first = stored value, second = visible label. Type-coerced: `true`/`false` → bool, `null` → null, numeric → number. | `option(1, "1 Star")` |
| `optionQuery(query)` | suggester family | Dataview-like query that resolves to a list of options dynamically. | `optionQuery("#tag")` |
| `defaultValue(value)` | text, textArea, number, date, time | Pre-fills when bind target is empty. | `defaultValue("Untitled")` |
| `placeholder(text)` | text, textArea, number | Greyed-out hint inside an empty input. | `placeholder("Enter name")` |
| `minValue(n)`, `maxValue(n)`, `stepSize(n)` | slider, number, progressBar | Numeric bounds and step. | `minValue(0), maxValue(10), stepSize(0.5)` |
| `addLabels` | slider | Show numeric labels alongside the slider. | `addLabels` |
| `allowOther` | suggester family | Accept user input outside the option list. | `allowOther` |
| `class(className)` | all input field types | Add CSS class for custom styling. Multiple supported. | `class(my-css-class)` |
| `showcase` | all | Display the bind target name + value beside the input — useful for debugging. | `showcase` |
| `title(text)` | all | Override the visible label / accessibility title. | `title("Priority")` |
| `useLinks(true\|false)` | suggester variants | Whether selected values are stored as `[[wikilinks]]`. | `useLinks(true)` |
| `onValue(v)`, `offValue(v)` | toggle | Override what gets written for true/false (default: `true`/`false` booleans). | `onValue("done"), offValue("todo")` |
Class names with special chars must be quoted: `class("group-1")`.
For value strings with special chars (commas, parens), use single or double quotes inside the argument: `option("yes, please", "Yes")`.
## View fields — schema
Inline syntax: `` `VIEW[expression][type]` ``. Default `type` is `math`; can be omitted for math expressions.
Types: `math`, `text`, `image`, `link`, plus `jsViewField` for arbitrary JS-driven views.
Property references inside `expression` use `{propertyName}` and follow the same bind target rules as inputs.
```markdown
`VIEW[round(number({distance} km, miles), 2)]`
`VIEW[**{title}**][text(renderMarkdown)]`
`VIEW[{cover}][image]`
`VIEW[{relatedNote}][link]`
JS View Fields
jsViewField is a code block that lets arbitrary JS render a value, with reactive bind targets. It needs JS Engine + enableJs: true.
```meta-bind-js-view bindTargets: distance: this#distanceKm hidden: true --- return Math.round(distance * 0.621371);
The runner injects each `bindTarget` as a top-level JS variable. The function returns the rendered value (string / number / DOM Node). Updates re-run when any binding changes.
Use cases: charts, computed labels, lookups in a contact list, anything beyond the math/text view fields.
## Meta Bind Embeds
A way to embed a note's interactive widgets *as if they belonged to the host file*. Standard `![[Note]]` keeps widgets bound to the source's frontmatter; meta-bind embeds re-bind to the **container** note.
Inline:
META_BIND_EMBED[[Note A]]
Block:
````
```meta-bind-embed[[Note A]]```
````
Use case: a "tracker template note" with INPUT/VIEW/BUTTON declarations, embedded into many host notes. Each host note gets its own bound state.
Caveat: deep nesting (embed-inside-embed) is limited; check current docs version.
## Settings (`data.json`) — the full set
| Key | Default | Effect |
|---|---|---|
| `devMode` | false | Dev-mode logs in console. |
| `ignoreCodeBlockRestrictions` | false | If true, Meta Bind also processes plain code blocks, not just its own. Risky. |
| `preferredDateFormat` | `YYYY-MM-DD` | Used by date inputs / view fields. |
| `firstWeekday` | `{index:1,name:"Monday",...}` | Calendar pickers. |
| `syncInterval` | 200 | ms between metadata sync passes. |
| `enableJs` | false | Toggles `inlineJS` / `js` actions and `jsViewField`. Required for any JS-driven element. |
| `viewFieldDisplayNullAsEmpty` | false | If true, missing values render as empty string instead of `null`. |
| `enableSyntaxHighlighting` | true | YAML highlighting in meta-bind code blocks. |
| `enableEditorRightClickMenu` | true | Right-click → "Insert input field/button". |
| `inputFieldTemplates` | [] | Reusable INPUT-field templates. Format: `{ id, declaration }`. Reference via `INPUT[id][overrides]`. |
| `buttonTemplates` | [] | Reusable button templates. Same fields as a button declaration, plus `id`. Reference via `` `BUTTON[id]` ``. |
| `excludedFolders` | `["templates"]` | Path-substring matches; Meta Bind skips files under those folders entirely. |
## ObsAPI — public methods
`const mb = app.plugins.getPlugin("obsidian-meta-bind-plugin").api;`
| Method | Purpose |
|---|---|
| `createField(filePath, opts)` | Generic field creator (input/view/jsView). |
| `createInputFieldMountable(filePath, opts)` | Build a mountable input field programmatically. |
| `createViewFieldMountable(filePath, opts)` | Build a mountable view field. |
| `createJsViewFieldMountable(filePath, opts)` | Build a mountable JS view field. |
| `createTableMountable(filePath, opts)` | Build a tracker/table mountable. |
| `createButtonMountable(filePath, opts)` | Build a mountable button (use this when rendering programmatic buttons from another plugin). |
| `createButtonGroupMountable(filePath, opts)` | Group of buttons. |
| `createEmbedMountable(filePath, opts)` | Programmatic meta-bind-embed. |
| `createExcludedMountable(filePath)` | The "excluded folder" placeholder UI. |
| `createInlineFieldFromString(str, ...)` | Parse a `INPUT[...]` / `VIEW[...]` / `BUTTON[...]` string. |
| `createInlineFieldOfTypeFromString(type, str, ...)` | Same but with explicit kind. |
| `wrapInMDRC(mountable, container, ctx)` | Lifecycle helper — handles mount/unmount via Obsidian's MarkdownRenderChild. **Always use this** when mounting a Meta Bind component manually. |
| `constructMDRCWidget(mountable, ...)` | Lower-level CodeMirror 6 widget constructor (live-preview path). |
| `createBindTarget(storageType, filePath, propertyPath)` | Programmatic bind target. |
| `parseBindTarget(str)` | Parse a bind-target string. |
| `createNotePosition(...)` | Position reference for `insertIntoNote` etc. |
| `createSignal(initial)` | Reactive signal — fire-and-subscribe. |
| `getMetadata(bindTarget)` | Read frontmatter / memory at a bind target. |
| `setMetadata(bindTarget, value)` | Write. |
| `updateMetadata(bindTarget, fn)` | Read-modify-write. |
| `subscribeToMetadata(bindTarget, fn)` | React to changes. |
| `reactiveMetadata(bindTarget)` | Wrap a bind target in a `Signal` for reactive UIs. |
There is **no public `setButtonTemplate` / `registerButtonTemplate`**. To populate templates programmatically, you must reach into `mb.api.mb.buttonManager.buttonTemplates` (internal). See "zero data.json" pa
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [lexbritvin](https://github.com/lexbritvin)
- **Source:** [lexbritvin/obsidian-skills-pack](https://github.com/lexbritvin/obsidian-skills-pack)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.