# Obsidian Meta Bind

> Authoring buttons, input fields, view fields, and embeds for the Obsidian Meta Bind plugin. Use whenever the user asks to add an interactive control, button, input, viewfield, INPUT[...] / VIEW[...] / BUTTON[...] syntax, or a meta-bind code block. Covers schema, valid enum values, inline references, JS Engine integration, and the most common validation pitfalls (e.g. "Invalid input at 'style'", "…

- **Type:** Skill
- **Install:** `agentstack add skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [lexbritvin](https://agentstack.voostack.com/s/lexbritvin)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [lexbritvin](https://github.com/lexbritvin)
- **Source:** https://github.com/lexbritvin/obsidian-skills-pack/tree/main/skills/obsidian-meta-bind

## Install

```sh
agentstack add skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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-bind ` code 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:

1. **Meta Bind plugin installed and enabled.** Check `.obsidian/plugins/obsidian-meta-bind-plugin/manifest.json`.
2. **JS Engine plugin** installed and enabled if any button uses `action.type: inlineJS` or `js`. Without it, JS actions silently fail.
3. **Settings flag `enableJs: true`** in `.obsidian/plugins/obsidian-meta-bind-plugin/data.json`. JS-based buttons and JS view fields don't run otherwise.
4. **`excludedFolders`** in the same `data.json`: any path matching an entry is skipped by Meta Bind. Default contains `templates`. If a tracker file lives under `Templates/`, 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

```yaml
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.

```yaml
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

```yaml
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:`)

```yaml
action:
  type: sleep
  ms: 500
```

### `input` — type text at the cursor (in the editor)

```yaml
action:
  type: input
  str: "some text"
```

### `insertIntoNote` — insert text or Templater output at a line

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind
- Seller: https://agentstack.voostack.com/s/lexbritvin
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
