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
Method Description 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
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
Navigation Property
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
Always clean up in onClose() and onUnloadFile():
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 ));
}
Views extend Component, so use its lifecycle methods:
onload (): void {
// Register event handlers
this . registerEvent (
this . app . workspace . on ( 'file-open' , this . handleFileOpen . bind ( this ))
);
}
For popout window support, ensure your view handles window changes properly:
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