# Obsidian Vault API Deep Dive

This reference covers the TypeScript API for building plugins and advanced automation. All objects are available via `app` in the Obsidian runtime.

---

## Core Architecture

```
App
├── Vault (file system)
├── MetadataCache (parsed note data)
├── FileManager (file operations with link updating)
├── Workspace (UI panes)
├── Plugins (plugin registry)
├── Commands (command palette)
└── Internal
    ├── Settings
    ├── Themes
    └── Hotkeys
```

---

## Vault API

### File Discovery

```typescript
// All markdown files
const mdFiles: TFile[] = app.vault.getMarkdownFiles()

// All files (including attachments)
const allFiles: TAbstractFile[] = app.vault.getFiles()

// Specific file
const file = app.vault.getAbstractFileByPath("path/to/Note.md")
const file = app.vault.getFileByPath("path/to/Note.md")

// File exists?
const exists = app.vault.getAbstractFileByPath("Note.md") !== null
```

### Reading Files

```typescript
// Fresh read from disk
const content: string = await app.vault.read(file)

// Cached read (faster, may be stale)
const cached: string = await app.vault.cachedRead(file)

// Read binary
const binary: ArrayBuffer = await app.vault.readBinary(file)
```

### Writing Files

```typescript
// Create new file
const newFile = await app.vault.create("New Note.md", "# Hello\n\nContent")

// Create folder
await app.vault.createFolder("Projects/New Project")

// Overwrite file
await app.vault.modify(file, "New content")

// Atomic read-modify-write (preferred)
await app.vault.process(file, (data) => {
    return data + "\n\nAppended text"
})

// Append (not atomic)
await app.vault.append(file, "\nAppended text")

// Adapter-level append (lower level)
await app.vault.adapter.append(file.path, "\nText")
```

### Deleting Files

```typescript
// Permanent delete
await app.vault.delete(file)

// Move to Obsidian trash
await app.vault.trash(file, false)

// Move to system trash
await app.vault.trash(file, true)
```

### Renaming/Moving

```typescript
// Rename (preserves links via FileManager)
await app.fileManager.renameFile(file, "New Name.md")

// Move to folder
await app.fileManager.renameFile(file, "Folder/New Name.md")

// Generate unique path
const uniquePath = app.vault.getAvailablePathForAttachments("image", "png", "Folder/")
```

### File Events

```typescript
// File created
this.registerEvent(app.vault.on('create', (file: TAbstractFile) => {
    console.log('Created:', file.path)
}))

// File modified
this.registerEvent(app.vault.on('modify', (file: TAbstractFile) => {
    console.log('Modified:', file.path)
}))

// File deleted
this.registerEvent(app.vault.on('delete', (file: TAbstractFile) => {
    console.log('Deleted:', file.path)
}))

// File renamed
this.registerEvent(app.vault.on('rename', (file: TAbstractFile, oldPath: string) => {
    console.log('Renamed:', oldPath, '->', file.path)
}))

// File closed
this.registerEvent(app.vault.on('close', (file: TFile) => {
    console.log('Closed:', file.path)
}))
```

---

## MetadataCache API

### Reading Metadata

```typescript
// Get cache for a file
const cache: CachedMetadata = app.metadataCache.getFileCache(file)

// Returns:
interface CachedMetadata {
    frontmatter?: FrontMatterCache      // YAML frontmatter
    tags?: TagCache[]                    // Inline tags
    headings?: HeadingCache[]            // # Headings
    links?: ReferenceCache[]             // [[Wikilinks]]
    embeds?: ReferenceCache[]            // ![[Embeds]]
    blocks?: BlockCache[]                // ^block-ids
    sections?: SectionCache[]            // Markdown sections
    listItems?: ListItemCache[]          // List items
}
```

### Frontmatter Access

```typescript
const cache = app.metadataCache.getFileCache(file)

if (cache?.frontmatter) {
    const title = cache.frontmatter.title
    const tags = cache.frontmatter.tags  // Array or string
    const status = cache.frontmatter.status
}
```

### Link Resolution

```typescript
// Get destination file for a wikilink
const destFile = app.metadataCache.getFirstLinkpathDest("Target Note", sourcePath)

// Get all resolved links in the vault
const linkGraph = app.metadataCache.resolvedLinks
// { "path/to/Source.md": { "path/to/Target.md": 1 } }

// Get all unresolved links
const unresolved = app.metadataCache.unresolvedLinks

// Get backlinks to a file
const backlinks = app.metadataCache.getBacklinksForFile(file)
```

### Tags

```typescript
// All tags in vault with counts
const allTags = app.metadataCache.getTags()
// { "#project": 42, "#task": 15 }

// Get property types
const propInfo = app.metadataCache.getAllPropertyInfos()
```

### Metadata Events

```typescript
// Metadata changed (parsed)
this.registerEvent(app.metadataCache.on('changed', 
    (file: TFile, data: string, cache: CachedMetadata) => {
        console.log('Metadata updated:', file.path)
    }
))

// Metadata resolved (fully parsed)
this.registerEvent(app.metadataCache.on('resolve', 
    (file: TFile) => {
        console.log('Metadata resolved:', file.path)
    }
))

// File deleted (metadata removed)
this.registerEvent(app.metadataCache.on('deleted', 
    (file: TFile, prevCache: CachedMetadata) => {
        console.log('Metadata deleted:', file.path)
    }
))
```

---

## FileManager API

### Link Management

```typescript
// Rename file and update all links
await app.fileManager.renameFile(file, "New Name.md")

// Generate markdown link
const link = app.fileManager.generateMarkdownLink(file, sourcePath, subpath, alias)
// subpath: "#Heading" or "#^block-id"
// alias: display text

// Insert link at cursor
app.fileManager.insertMarkdownFile(activeView.editor, file)
```

### Frontmatter

```typescript
// Process frontmatter
await app.fileManager.processFrontMatter(file, (frontmatter) => {
    frontmatter.status = "done"
    frontmatter.completed = new Date().toISOString()
})
```

---

## Workspace API

### Panes and Leaves

```typescript
// Get active file
const activeFile = app.workspace.getActiveFile()

// Get active leaf (pane)
const activeLeaf = app.workspace.getMostRecentLeaf()

// Get all leaves
const allLeaves = app.workspace.getLeavesOfType('markdown')

// Open file in leaf
const leaf = app.workspace.getLeaf('tab')  // or 'split', 'window'
await leaf.openFile(file, { active: true })

// Open link text
app.workspace.openLinkText("Target Note", sourcePath)
app.workspace.openLinkText("Target Note#Heading", sourcePath)

// Set view state
await leaf.setViewState({ 
    type: 'markdown', 
    state: { file: file.path }
})

// Reveal file in sidebar
app.workspace.revealLeaf(leaf)
```

### UI Components

```typescript
// Show notice (toast)
new Notice('Hello world', 5000)  // 5 second duration

// Show modal
const modal = new Modal(app)
modal.contentEl.createEl('h2', { text: 'Title' })
modal.contentEl.createEl('p', { text: 'Content' })
modal.open()

// Show suggester
const choices = ['A', 'B', 'C']
const choice = await new Promise(resolve => {
    new SuggestModal(app, choices, (selected) => resolve(selected))
})

// Show prompt
const result = await new Promise(resolve => {
    new PromptModal(app, 'Enter value:', (value) => resolve(value))
})
```

---

## Commands API

```typescript
// Add command to palette
this.addCommand({
    id: 'my-command',
    name: 'My Command',
    icon: 'star',
    hotkeys: [{ modifiers: ['Mod'], key: 'm' }],
    callback: () => {
        new Notice('Command executed!')
    },
    checkCallback: (checking: boolean) => {
        // Return true if command is available
        if (checking) return true
        // Execute
        new Notice('Checked command!')
    }
})

// Execute existing command by ID
app.commands.executeCommandById('app:toggle-left-sidebar')

// List all commands
const commands = app.commands.listCommands()
```

---

## Plugin API

```typescript
// Access another plugin
const dataview = app.plugins.getPlugin('dataview')
if (dataview) {
    const api = dataview.api
    const pages = api.pages('#tag')
}

// Enable/disable plugin
await app.plugins.enablePlugin('plugin-id')
await app.plugins.disablePlugin('plugin-id')

// Install plugin (community)
await app.plugins.installPlugin('plugin-id')
```

---

## Editor API

```typescript
// Get active editor
const activeView = app.workspace.getActiveViewOfType(MarkdownView)
const editor = activeView?.editor

if (editor) {
    // Get cursor position
    const cursor = editor.getCursor()
    
    // Get selection
    const selection = editor.getSelection()
    
    // Replace selection
    editor.replaceSelection('new text')
    
    // Insert at cursor
    editor.replaceRange('text', cursor)
    
    // Get line
    const line = editor.getLine(cursor.line)
    
    // Set cursor
    editor.setCursor({ line: 0, ch: 0 })
    
    // Scroll to cursor
    editor.scrollTo(0, cursor.line)
    
    // Get value
    const value = editor.getValue()
    
    // Set value
    editor.setValue('new content')
}
```

---

## DataAdapter API

```typescript
// Low-level file system access
const adapter = app.vault.adapter

// Read
const text = await adapter.read('path/to/file.md')
const binary = await adapter.readBinary('path/to/image.png')

// Write
await adapter.write('path/to/file.md', 'content')
await adapter.writeBinary('path/to/image.png', arrayBuffer)

// Delete
await adapter.remove('path/to/file.md')

// Exists
const exists = await adapter.exists('path/to/file.md')

// Stat
const stats = await adapter.stat('path/to/file.md')
// { ctime: number, mtime: number, size: number, type: 'file' | 'folder' }

// List directory
const listing = await adapter.list('path/to/folder')
// { files: string[], folders: string[] }

// Read directory recursively
const files = await adapter.listRecursive('path/to/folder')
```

---

## Events System

```typescript
// Base Events class (used by Vault, MetadataCache, etc.)
class Events {
    on(event: string, callback: (...args: any[]) => void): EventRef
    off(event: string, callback: (...args: any[]) => void): void
    offref(ref: EventRef): void
    trigger(event: string, ...args: any[]): void
}
```

In plugins, use `this.registerEvent()` to auto-cleanup:

```typescript
this.registerEvent(app.vault.on('create', callback))
// Automatically unregistered when plugin is disabled
```

---

## Complete Plugin Skeleton

```typescript
import { Plugin, TFile, Notice, Modal } from 'obsidian'

interface MyPluginSettings {
    folder: string
    template: string
}

const DEFAULT_SETTINGS: MyPluginSettings = {
    folder: 'Inbox',
    template: 'Default'
}

export default class MyPlugin extends Plugin {
    settings: MyPluginSettings

    async onload() {
        await this.loadSettings()

        // Add ribbon icon
        this.addRibbonIcon('dice', 'My Plugin', () => {
            new Notice('Plugin activated!')
        })

        // Add command
        this.addCommand({
            id: 'create-from-template',
            name: 'Create from Template',
            callback: () => this.createFromTemplate()
        })

        // Register events
        this.registerEvent(
            this.app.vault.on('create', (file) => {
                if (file instanceof TFile && file.extension === 'md') {
                    console.log('New note:', file.path)
                }
            })
        )

        // Add settings tab
        this.addSettingTab(new MySettingTab(this.app, this))
    }

    async createFromTemplate() {
        const { vault } = this.app
        const path = `${this.settings.folder}/New Note.md`
        const content = `# New Note\n\nCreated: ${new Date().toISOString()}`
        const file = await vault.create(path, content)
        new Notice(`Created: ${file.path}`)
    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
    }

    async saveSettings() {
        await this.saveData(this.settings)
    }

    onunload() {
        // Cleanup handled automatically by registerEvent
    }
}
```

---

## Type Reference

### TFile
```typescript
interface TFile extends TAbstractFile {
    basename: string       // Name without extension
    extension: string     // File extension (lowercase)
    stat: FileStats       // { ctime, mtime, size }
}
```

### TFolder
```typescript
interface TFolder extends TAbstractFile {
    children: TAbstractFile[]
}
```

### CachedMetadata Fields
```typescript
interface CachedMetadata {
    frontmatter?: FrontMatterCache
    tags?: TagCache[]
    headings?: HeadingCache[]
    links?: ReferenceCache[]
    embeds?: ReferenceCache[]
    blocks?: BlockCache[]
    sections?: SectionCache[]
    listItems?: ListItemCache[]
}

interface FrontMatterCache {
    [key: string]: any
    position?: Pos
}

interface ReferenceCache {
    link: string
    displayText?: string
    original: string
    position: Pos
}

interface HeadingCache {
    heading: string
    level: number
    position: Pos
}
```

---

*For the complete API, see the official Obsidian Developer Docs at https://docs.obsidian.md/*
