Skip to main content
Obsidian’s architecture is organized into major modules that work together to provide a complete note-taking experience. Understanding these modules and how they interact is essential for effective plugin development.

The App Object

The App object is the global singleton that owns all other modules. You access it via this.app inside your plugin:
export class App {
  keymap: Keymap;
  scope: Scope;
  workspace: Workspace;
  vault: Vault;
  metadataCache: MetadataCache;
  fileManager: FileManager;
  lastEvent: UserEvent | null;
  renderContext: RenderContext;
  secretStorage: SecretStorage;
  
  isDarkMode(): boolean;
  loadLocalStorage(key: string): any | null;
  saveLocalStorage(key: string, data: unknown | null): void;
}
The App object is available as this.app in all Plugin and Component instances.

Major Modules

Vault

The Vault interface lets you interact with files and folders in the vault:
// Read a file
const content = await this.app.vault.read(file);

// Create a file
const newFile = await this.app.vault.create('path/to/file.md', 'content');

// Modify a file
await this.app.vault.modify(file, 'new content');

// Delete a file
await this.app.vault.delete(file);

// Get all markdown files
const markdownFiles = this.app.vault.getMarkdownFiles();

// Get file by path
const file = this.app.vault.getAbstractFileByPath('path/to/file.md');

File Operations

Create, read, update, and delete files in the vault

File Discovery

List and search for files and folders

Workspace

The Workspace interface manages the UI layout and panes:
// Get the active file
const activeFile = this.app.workspace.getActiveFile();

// Get the active view
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);

// Open a file
await this.app.workspace.openLinkText('file.md', '', false);

// Get a leaf (pane)
const leaf = this.app.workspace.getLeaf(false);

// Listen to workspace events
this.registerEvent(
  this.app.workspace.on('active-leaf-change', (leaf) => {
    console.log('Active leaf changed');
  })
);

Pane Management

Create, manage, and navigate between workspace panes

View Access

Access and interact with active views and editors

MetadataCache

The MetadataCache contains cached metadata about each markdown file, including headings, links, embeds, tags, and blocks:
// Get file metadata
const cache = this.app.metadataCache.getFileCache(file);

// Access frontmatter
const frontmatter = cache?.frontmatter;

// Access headings
const headings = cache?.headings;

// Access links
const links = cache?.links;

// Access tags
const tags = cache?.tags;

// Listen to cache changes
this.registerEvent(
  this.app.metadataCache.on('changed', (file) => {
    console.log('Metadata changed for:', file.path);
  })
);
The MetadataCache is updated asynchronously. If you modify a file, the cache may not be updated immediately.

FileManager

The FileManager provides high-level operations for managing files:
// Rename a file (updates all links)
await this.app.fileManager.renameFile(file, 'new-name.md');

// Generate a markdown link
const link = this.app.fileManager.generateMarkdownLink(
  file,
  'source-path.md',
  '#heading',
  'Display Text'
);

// Process frontmatter
await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
  frontmatter['key'] = 'value';
  delete frontmatter['old-key'];
});

// Get new file parent folder
const folder = this.app.fileManager.getNewFileParent('source-path.md');

Smart Renaming

Rename files and automatically update all links

Link Generation

Generate links according to user preferences

Access Patterns

All major modules are accessed through the App object:
export default class MyPlugin extends Plugin {
  async onload() {
    // Access vault
    const files = this.app.vault.getMarkdownFiles();
    
    // Access workspace
    const activeFile = this.app.workspace.getActiveFile();
    
    // Access metadata cache
    if (activeFile) {
      const cache = this.app.metadataCache.getFileCache(activeFile);
    }
    
    // Access file manager
    const folder = this.app.fileManager.getNewFileParent('');
  }
}

Module Interaction

Here’s how the modules typically work together:
async processAllNotes() {
  // Use Vault to get all files
  const files = this.app.vault.getMarkdownFiles();
  
  for (const file of files) {
    // Use MetadataCache to get metadata
    const cache = this.app.metadataCache.getFileCache(file);
    
    if (cache?.frontmatter?.tags?.includes('process')) {
      // Use Vault to read content
      const content = await this.app.vault.read(file);
      
      // Process content...
      const newContent = this.processContent(content);
      
      // Use Vault to write back
      await this.app.vault.modify(file, newContent);
    }
  }
}
async createNote(title: string) {
  // Use FileManager to determine location
  const folder = this.app.fileManager.getNewFileParent('', title + '.md');
  
  // Use Vault to create the file
  const file = await this.app.vault.create(
    folder.path + '/' + title + '.md',
    '# ' + title
  );
  
  // Use Workspace to open the file
  await this.app.workspace.openLinkText(file.path, '', false);
  
  return file;
}

Additional Components

Keymap

Manages keyboard shortcuts and scopes:
// Check if modifier key is pressed
const isCtrlPressed = Keymap.isModifier(evt, 'Mod');

// Check what type of pane to open
const paneType = Keymap.isModEvent(evt);

Scope

Manages keyboard event handling in different contexts:
const scope = new Scope(this.app.scope);
scope.register(['Mod'], 'K', () => {
  console.log('Mod+K pressed');
  return false;
});
this.app.keymap.pushScope(scope);

RenderContext

Utility functions for rendering values within the app (primarily for Bases/Properties):
const renderContext = this.app.renderContext;
// Used for rendering property values and formulas

Best Practices

Use FileManager for Renames

Always use FileManager.renameFile() instead of direct vault operations to ensure links are updated.

Cache Awareness

Remember that MetadataCache is updated asynchronously. Don’t rely on immediate updates after file modifications.

Event Registration

Use registerEvent() to listen to module events for automatic cleanup when your plugin unloads.

Type Safety

Use getActiveViewOfType() instead of type assertions for safer view access.

Complete Example

import { Plugin, TFile, MarkdownView } from 'obsidian';

export default class ArchitectureExample extends Plugin {
  async onload() {
    this.addCommand({
      id: 'example-architecture',
      name: 'Example: Show Architecture Usage',
      callback: async () => {
        // Workspace: Get active file
        const activeFile = this.app.workspace.getActiveFile();
        if (!activeFile) return;
        
        // MetadataCache: Get metadata
        const cache = this.app.metadataCache.getFileCache(activeFile);
        console.log('Headings:', cache?.headings);
        
        // Vault: Read content
        const content = await this.app.vault.read(activeFile);
        console.log('Content length:', content.length);
        
        // FileManager: Generate link
        const link = this.app.fileManager.generateMarkdownLink(
          activeFile,
          '',
          '',
          ''
        );
        console.log('Link:', link);
      }
    });
    
    // Register workspace event
    this.registerEvent(
      this.app.workspace.on('file-open', (file) => {
        if (file) {
          console.log('Opened:', file.path);
        }
      })
    );
    
    // Register metadata cache event
    this.registerEvent(
      this.app.metadataCache.on('changed', (file) => {
        console.log('Metadata changed:', file.path);
      })
    );
  }
}

Plugin Lifecycle

Learn about plugin initialization and lifecycle methods

Events

Understand the event system and how modules communicate

Build docs developers (and LLMs) love