# MD Butler

MD Butler

**MD Butler** is an Obsidian plugin that automatically manages YAML frontmatter fields for all your notes. Event-driven, configurable, and a reliable alternative to Templater formulas for consistent metadata. ## Features - **Event-driven metadata**: Automatically update metadata on file open, modify, rename, or bulk operations - **Configurable fields**: 8 built-in fields (FileName, FilePath, DateCreated, NoteType, LastModified, LastMoved, Source, Tags) plus unlimited custom fields - **Template system**: Powerful `{{template}}` expressions with pipe-chain functions (`{{title | upper | replace:a,b}}`) - Date formatting: `{{date:YYYY-MM-DD}}`, `{{now+7d}}` - Frontmatter access: `{{frontmatter:key}}` - File properties: `{{title}}`, `{{fileName}}`, `{{fileFolder}}` (alias `{{folder}}`), `{{filePath}}`, `{{oldPath}}`, `{{oldName}}`, `{{oldFolder}}` - Pipe functions: `upper`, `lower`, `trim`, `replace:X,Y`, `default:VAL`, `substr:S,E`, `date:FMT` - **Conditional fields**: Apply fields only when conditions are met (frontmatter, path, filename, or folder rules). Auto-removes the YAML key when the condition no longer matches - **Event binding**: Per-field event checkboxes (open, modify, rename, bulk) - **Folder filtering**: Include or exclude entire folder trees - **Processing scope**: "New notes only" (skip files with DateCreated) or "All files" - **Force apply**: Overwrite all metadata across the vault - **Full repair**: Force-apply → cleanup orphaned keys → consistency check — all in one command - **Do-Not-Touch list**: Protect specific YAML keys from being deleted or overwritten - **YAML key migration**: Automatically migrate old YAML keys to new ones when you rename a field - **Orphaned key cleanup**: Remove leftover YAML keys from deleted fields - **Consistency check**: Scans your vault and reports files with missing fields - **Field requirement modes**: `required` (default) or `if-exists` — `if-exists` only flags a field when the YAML key exists but is empty (useful for fields like LastMoved that are only set under certain circumstances) - **Drag-and-drop UI**: Reorder fields easily in settings ## Installation ### From Obsidian Community Plugins 1. Open Obsidian → Settings → Community Plugins 2. Browse and search for **MD Butler** 3. Install and enable it — future updates arrive automatically ### Manual installation 1. Download the latest release from [GitHub](https://github.com/PPeapps/MD-Butler/releases) 2. Copy `main.js`, `manifest.json`, and `styles.css` to `VaultFolder/.obsidian/plugins/md-butler/` 3. Reload Obsidian and enable the plugin ## Commands | Command | ID | Description | |---------|----|-------------| | Apply Metadata to all Notes | `md-butler:apply-all` | Update metadata for all notes respecting scope & filters | | Force-apply metadata to all notes | `md-butler:force-apply` | Overwrite all metadata fields (ignores scope) | | Full repair | `md-butler:full-repair` | Force-apply → cleanup → consistency check | | Vault Consistency Check | `md-butler:check-consistency` | Scan vault for missing fields | | Clean up old YAML keys | `md-butler:cleanup-keys` | Remove orphaned YAML keys from deleted fields | ## Settings | Setting | Description | |---------|-------------| | Date Format | Moment.js format string for all date values | | Processing Mode | "New notes only" or "All files" | | Metadata Fields | Drag-and-drop list of enabled fields with templates, events, and conditions | | Excluded Folders | Folders to skip (when in exclude mode) | | Included Folders | Folders to process (when in include mode) | | Protected YAML Keys | Keys the plugin must never delete or overwrite | ## Field Types ### Built-in fields | Field ID | YAML Key | Default Events | Description | |----------|----------|---------------|-------------| | fileName | FileName | open, rename, bulk | Current file name without extension | | filePath | FilePath | open, rename, bulk | Full vault path to the file | | dateCreated | DateCreated | open, rename, bulk | Date the file was first created | | noteType | NoteType | open, rename, bulk | Select field: Project, Note (default), Task, Person, Meeting, Reference | | lastModified | LastModified | modify, rename, bulk | Date the file was last modified | | lastMoved | LastMoved | rename | Date the file was last renamed/moved (`requirement: if-exists`) | | source | Source | open, rename, bulk | Custom field (disabled by default) | | tags | Tags | modify, bulk | Custom field (disabled by default) | ### Select fields Fields can be `type: "text"` (default) or `type: "select"`. Select fields have: - Configurable `options[]` — the allowed values (one per line in settings) - `defaultValue` — set once for new notes if no template is active - Template dropdown — pick one option to always overwrite with that value - Smart default: when template is "(none)", the writer sets `defaultValue ?? options[0]` for new notes Built-in NoteType is a select field. Create your own (Status, Priority, Area, etc.). ### Custom fields Create unlimited custom fields with: - Custom YAML key name - Type selection (text or select) - Template expression or default value - Event binding (open/modify/rename/bulk) - Conditions for selective application - Drag-and-drop reordering ### Requirement modes Each field has a `requirement` mode that controls how the Vault Consistency Check flags it: | Mode | Behavior | |------|----------| | `required` (default) | Field must exist — reported as missing if the YAML key is absent | | `if-exists` | Field is only flagged if the YAML key exists but is empty (`null`, `""`, or `{}`) | Use `if-exists` for fields that are only set under certain circumstances and should not be flagged when simply absent — e.g. `LastMoved` (only set when a file has been moved) or optional metadata that is added later. ## Template Reference ``` {{date:YYYY-MM-DD}} → 2026-06-17 {{now+7d}} → 2026-06-24 (relative date) {{now-1w}} → 2026-06-10 (relative date, supports d/w/M/y) {{title}} → File name without extension {{fileName}} → File name with extension {{fileFolder}} → Parent folder path (alias: {{folder}}) {{filePath}} → Full vault path {{oldPath}} → Previous path (rename events only) {{oldFolder}} → Previous folder (rename events only) {{oldName}} → Previous file name (rename events only) {{frontmatter:key}} → Value of any frontmatter key {{title | upper}} → UPPERCASE {{title | lower}} → lowercase {{title | trim}} → trimmed {{title | replace:a,b}} → Replace all 'a' with 'b' {{title | default:Untitled}} → Fallback if empty {{title | substr:0,5}} → First 5 characters {{date:YYYY | upper}} → Chained: format then uppercase ``` ## Conditions Reference | Type | Operators | Description | |------|-----------|-------------| | always | — | Always apply (default) | | frontmatter | exists, equals, matches, contains | Check a frontmatter key value | | path | equals, matches, contains | Check file path | | filename | equals, matches, contains | Check file name | | folder | equals, matches, contains | Check parent folder | Fields with conditions are **auto-removed** from frontmatter when their condition no longer matches — the YAML key is deleted on the next event that triggers a write. ## Development ```bash # Clone the repo git clone https://github.com/PPeapps/MD-Butler cd md-butler # Install dependencies npm install # Development (watch mode) npm run dev # Production build npm run build # Lint npm run lint ``` ## Requirements - Obsidian v1.8.0 or later - No external dependencies (uses Obsidian API only) ## License MIT