Skip to main content
Custom views allow you to create unique interfaces for displaying and interacting with data in Obsidian. This guide covers the core concepts and API for building custom views.

Overview

Obsidian provides two main base classes for creating views:
  • ItemView: A general-purpose view with a content container
  • FileView: A view that’s associated with a specific file

Creating a Basic View

Extending ItemView

The ItemView class is the foundation for most custom views:
import { ItemView, WorkspaceLeaf } from 'obsidian';

export const VIEW_TYPE_EXAMPLE = 'example-view';

export class ExampleView extends ItemView {
  constructor(leaf: WorkspaceLeaf) {
    super(leaf);
  }

  getViewType(): string {
    return VIEW_TYPE_EXAMPLE;
  }

  getDisplayText(): string {
    return 'Example view';
  }

  async onOpen(): Promise<void> {
    const container = this.containerEl.children[1];
    container.empty();
    container.createEl('h4', { text: 'Example view' });
  }

  async onClose(): Promise<void> {
    // Clean up view resources
  }
}
The contentEl property provides direct access to the view’s content container for building your UI.

File-Based Views

Extending FileView

For views that work with specific files, extend FileView:
import { FileView, TFile, WorkspaceLeaf } from 'obsidian';

export class CustomFileView extends FileView {
  file: TFile | null = null;

  getViewType(): string {
    return 'custom-file-view';
  }

  getDisplayText(): string {
    return this.file?.basename || 'Custom view';
  }

  async onLoadFile(file: TFile): Promise<void> {
    // Called when a file is loaded into this view
    this.file = file;
    const content = await this.app.vault.read(file);
    this.renderContent(content);
  }

  async onUnloadFile(file: TFile): Promise<void> {
    // Clean up when switching away from a file
    this.file = null;
  }

  canAcceptExtension(extension: string): boolean {
    return extension === 'custom';
  }

  private renderContent(content: string): void {
    this.contentEl.empty();
    this.contentEl.createEl('div', { text: content });
  }
}

Key FileView Methods

MethodDescription
onLoadFile(file)Called when a file is loaded
onUnloadFile(file)Called when switching to another file
onRename(file)Called when the file is renamed
canAcceptExtension(ext)Determines which file types this view accepts

Registering Views

Register with Plugin

Register your custom view in your plugin’s onload method:
import { Plugin } from 'obsidian';
import { ExampleView, VIEW_TYPE_EXAMPLE } from './view';

export default class ExamplePlugin extends Plugin {
  async onload() {
    this.registerView(
      VIEW_TYPE_EXAMPLE,
      (leaf) => new ExampleView(leaf)
    );

    // Add ribbon icon to activate view
    this.addRibbonIcon('dice', 'Open example view', () => {
      this.activateView();
    });
  }

  async activateView() {
    const { workspace } = this.app;

    let leaf = workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE)[0];
    if (!leaf) {
      const rightLeaf = workspace.getRightLeaf(false);
      if (rightLeaf) {
        leaf = rightLeaf;
        await leaf.setViewState({
          type: VIEW_TYPE_EXAMPLE,
          active: true,
        });
      }
    }

    if (leaf) {
      workspace.revealLeaf(leaf);
    }
  }
}

View State Management

Saving and Restoring State

Implement getState() and setState() to persist view state:
export class StatefulView extends ItemView {
  private scrollPosition: number = 0;

  getState(): any {
    return {
      scrollPosition: this.scrollPosition
    };
  }

  async setState(state: any, result: ViewStateResult): Promise<void> {
    this.scrollPosition = state.scrollPosition || 0;
    // Restore view to previous state
    this.containerEl.scrollTop = this.scrollPosition;
  }

  async onClose(): Promise<void> {
    // Save current scroll position
    this.scrollPosition = this.containerEl.scrollTop;
  }
}

View Actions

Adding Action Buttons

Add custom action buttons to your view’s header:
class ActionView extends ItemView {
  async onOpen(): Promise<void> {
    // Add action button
    this.addAction('refresh-cw', 'Refresh', () => {
      this.refresh();
    });

    this.addAction('download', 'Export', () => {
      this.export();
    });
  }

  private refresh(): void {
    // Refresh view content
  }

  private export(): void {
    // Export view data
  }
}

Handling Navigation

Set the navigation property to indicate if your view can be navigated:
export class NavigableView extends FileView {
  navigation = true; // Allow navigation history

  getViewType(): string {
    return 'navigable-view';
  }
}
When navigation is true, the view participates in forward/back navigation history.

Best Practices

1
Clean Up Resources
2
Always clean up in onClose() and onUnloadFile():
3
async onClose(): Promise<void> {
  // Clear intervals
  if (this.intervalId) {
    window.clearInterval(this.intervalId);
  }
  // Remove event listeners
  this.eventRefs.forEach(ref => this.app.workspace.offref(ref));
}
4
Use Component Lifecycle
5
Views extend Component, so use its lifecycle methods:
6
onload(): void {
  // Register event handlers
  this.registerEvent(
    this.app.workspace.on('file-open', this.handleFileOpen.bind(this))
  );
}
7
Handle Window Migration
8
For popout window support, ensure your view handles window changes properly:
9
onResize(): void {
  // Recalculate layout when view is resized
  this.updateLayout();
}

Working with Files

Learn to read and write files in the vault

UI Components

Build settings and UI elements

Build docs developers (and LLMs) love