AgentStack
SKILL verified MIT Self-run

Obsidian Meta Bind

skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind · by lexbritvin

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'", "…

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-lexbritvin-obsidian-skills-pack-obsidian-meta-bind

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Are you the author of Obsidian Meta Bind? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

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.

Versions

  • v0.1.0 Imported from the upstream source.