### Install Plugin Dependencies Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Install the necessary Node.js dependencies for the plugin. This command should be run from within the plugin's directory. ```bash npm install ``` -------------------------------- ### Add React dependencies Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Install React and ReactDOM as project dependencies. This is the first step to enable React in your plugin. ```bash npm install react react-dom ``` -------------------------------- ### Emoji List View Plugin Example Source: https://docs.obsidian.md/Plugins/Editor/Decorations This view plugin example manages emoji decorations for list items. It rebuilds decorations when the document or viewport changes, optimizing performance. ```typescript import { syntaxTree, } from '@codemirror/language'; import { RangeSetBuilder, } from '@codemirror/state'; import { Decoration, DecorationSet, EditorView, PluginSpec, PluginValue, ViewPlugin, ViewUpdate, WidgetType, } from '@codemirror/view'; import { EmojiWidget } from 'emoji'; class EmojiListPlugin implements PluginValue { decorations: DecorationSet; constructor(view: EditorView) { this.decorations = this.buildDecorations(view); } update(update: ViewUpdate) { if (update.docChanged || update.viewportChanged) { this.decorations = this.buildDecorations(update.view); } } destroy() {} buildDecorations(view: EditorView): DecorationSet { const builder = new RangeSetBuilder(); for (let { from, to } of view.visibleRanges) { syntaxTree(view.state).iterate({ from, to, enter(node) { if (node.type.name.startsWith('list')) { // Position of the '-' or the '*'. const listCharFrom = node.from - 2; builder.add( listCharFrom, listCharFrom + 1, Decoration.replace({ widget: new EmojiWidget(), }) ); } }, }); } return builder.finish(); } } const pluginSpec: PluginSpec = { decorations: (value: EmojiListPlugin) => value.decorations, }; export const emojiListPlugin = ViewPlugin.fromClass( EmojiListPlugin, pluginSpec ); ``` -------------------------------- ### Open a Modal from a Plugin Command Source: https://docs.obsidian.md/Plugins/User+interface/Modals Instantiate your custom modal class and call the `open()` method to display it. This example shows how to trigger the modal via a plugin command. ```typescript import { Plugin } from 'obsidian'; import { ExampleModal } from './modal'; export default class ExamplePlugin extends Plugin { async onload() { this.addCommand({ id: 'display-modal', name: 'Display modal', callback: () => { new ExampleModal(this.app).open(); }, }); } } ``` -------------------------------- ### Install Svelte dependencies Source: https://docs.obsidian.md/Plugins/Getting+started/Use+Svelte+in+your+plugin Add Svelte and its related build tools to your plugin's development dependencies. ```bash npm install --save-dev svelte svelte-preprocess esbuild-svelte svelte-check ``` -------------------------------- ### Compile Plugin with Development Server Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Compile the plugin's source code and start a development server. This command will automatically rebuild the plugin when source files are modified. ```bash npm run dev ``` -------------------------------- ### Complete settings tab implementation with SecretComponent Source: https://docs.obsidian.md/plugins/guides/secret-storage A full example of a plugin settings tab that integrates SecretComponent for secure secret management. ```typescript import { App, PluginSettingTab, SecretComponent, Setting } from "obsidian"; import MyPlugin from "./main"; export interface MyPluginSettings { mySetting: string; } export class SampleSettingTab extends PluginSettingTab { plugin: MyPlugin; constructor(app: App, plugin: MyPlugin) { super(app, plugin); this.plugin = plugin; } display(): void { const { containerEl } = this; containerEl.empty(); new Setting(containerEl) .setName('API key') .setDesc('Select a secret from SecretStorage') .addComponent(el => new SecretComponent(this.app, el) .setValue(this.plugin.settings.mySetting) .onChange(value => { this.plugin.settings.mySetting = value; this.plugin.saveSettings(); })); } } ``` -------------------------------- ### Create a Slider Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Implement a slider for numeric input. This example shows how to add a dynamic tooltip that updates with the slider's value. ```typescript new Setting(containerEl) .setName('Slider') .setDesc('with tooltip') .addSlider(slider => slider.setDynamicTooltip() ); ``` -------------------------------- ### Create a Svelte component Source: https://docs.obsidian.md/Plugins/Getting+started/Use+Svelte+in+your+plugin Define a basic Svelte component with state and props. This example includes a simple counter. ```svelte
My number is {count}!
``` -------------------------------- ### Illustrating State Changes Source: https://docs.obsidian.md/Plugins/Editor/State+management This example demonstrates how direct string assignments lose previous states, highlighting the need for a history-based approach for undo functionality. ```javascript let note = ''; note = 'Heading' note = '# Heading' note = '## Heading' // How to undo this? ``` -------------------------------- ### CSS Fallback Example for RTL Support Source: https://docs.obsidian.md/Plugins/User+interface/Right-to-left Use `@supports` to conditionally apply styles for newer CSS features like `:dir()`. This prevents older Obsidian versions from breaking. Split rules into fallback and modern versions. ```css .supported, .unsupported { /* this won't run */ } .supported { /* this will run */ } .unsupported { /* this won't run */ } @supports selector(:dir(*)) { /* will run if :dir() is supported */ } ``` -------------------------------- ### Initialize Plugin with Empty onload - TypeScript Source: https://docs.obsidian.md/plugins/guides/bases-view Start with a minimal plugin structure by removing unnecessary code and leaving only the onload function. ```typescript export default class MyPlugin extends Plugin { async onload() { } } ``` -------------------------------- ### Emoji List State Field Example Source: https://docs.obsidian.md/Plugins/Editor/Decorations This state field example demonstrates how to add emoji decorations to list items in the editor. It iterates through the syntax tree and adds a widget for list nodes. ```typescript import { syntaxTree, } from '@codemirror/language'; import { Extension, RangeSetBuilder, StateField, Transaction, } from '@codemirror/state'; import { Decoration, DecorationSet, EditorView, WidgetType, } from '@codemirror/view'; import { EmojiWidget } from 'emoji'; export const emojiListField = StateField.define({ create(state): DecorationSet { return Decoration.none; }, update(oldState: DecorationSet, transaction: Transaction): DecorationSet { const builder = new RangeSetBuilder(); syntaxTree(transaction.state).iterate({ enter(node) { if (node.type.name.startsWith('list')) { // Position of the '-' or the '*'. const listCharFrom = node.from - 2; builder.add( listCharFrom, listCharFrom + 1, Decoration.replace({ widget: new EmojiWidget(), }) ); } }, }); return builder.finish(); }, provide(field: StateField): Extension { return EditorView.decorations.from(field); }, }); ``` -------------------------------- ### Importing Moment.js in Obsidian Plugin Source: https://docs.obsidian.md/Plugins/Events Import the Moment.js library directly from the Obsidian API for date and time manipulation. No external installation is required. ```typescript import { moment } from 'obsidian'; ``` -------------------------------- ### Dispatching a Transaction to the Editor View Source: https://docs.obsidian.md/Plugins/Editor/State+management This example demonstrates how to dispatch a transaction to the editor view, grouping multiple state changes into a single undoable operation. This is useful for features like surrounding selected text with quotes. ```javascript view.dispatch({ changes: [ { from: selectionStart, insert: `"` }, { from: selectionEnd, insert: `"` } ] }); ``` -------------------------------- ### Add React type definitions Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Install the development type definitions for React to enable type checking and autocompletion in your TypeScript project. ```bash npm install --save-dev @types/react @types/react-dom ``` -------------------------------- ### Add Button Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Adds a button to the settings tab that triggers an action when clicked. This example shows a button that displays a notice. ```typescript new Setting(containerEl) .setName('Button') .setDesc('With extra button') .addButton(button => button .setButtonText('Click me!') .onClick(() => { new Notice('This is a notice!'); }) ) ); ``` -------------------------------- ### Get All Files Source: https://docs.obsidian.md/Plugins/Vault Retrieves all files within the vault, regardless of their type. Use this when you need to process all documents, not just Markdown. ```APIDOC ## Get All Files ### Description Retrieves all files within the vault, regardless of their type. Use this when you need to process all documents, not just Markdown. ### Method `app.vault.getFiles()` ### Returns - `TFile[]`: An array of TFile objects representing all files in the vault. ``` -------------------------------- ### Initialize a Setting Tab Container Source: https://docs.obsidian.md/Plugins/User+interface/HTML+elements This snippet shows how to get the container element for a plugin's settings tab. This container is an `HTMLElement` that can be used to create custom UI elements. ```typescript import { App, PluginSettingTab } from 'obsidian'; class ExampleSettingTab extends PluginSettingTab { plugin: ExamplePlugin; constructor(app: App, plugin: ExamplePlugin) { super(app, plugin); this.plugin = plugin; } display(): void { // highlight-next-line let { containerEl } = this; // ... } } ``` -------------------------------- ### Handle User Input Callback Source: https://docs.obsidian.md/Plugins/User+interface/Modals Process the data submitted from a modal by defining a callback function. This example displays a notice with the provided name. ```typescript new ExampleModal(this.app, (result) => { new Notice(`Hello, ${result}!`); }).open(); ``` -------------------------------- ### Recording State Changes with ChangeSpec Source: https://docs.obsidian.md/Plugins/Editor/State+management This TypeScript example shows how to record state changes using `ChangeSpec` to build a history that supports undo operations. ```typescript const changes: ChangeSpec[] = []; changes.push({ from: 0, insert: 'Heading' }); changes.push({ from: 0, insert: '# ' }); changes.push({ from: 0, insert: '#' }); ``` -------------------------------- ### Add Configuration Options to Bases View - TypeScript Source: https://docs.obsidian.md/plugins/guides/bases-view Enhance a custom Bases view by adding configuration options. This example demonstrates how to include a text input for a 'Property separator' in the view's settings menu, with a default value. ```typescript export default class MyPlugin extends Plugin { async onload() { // Tell Obsidian about the new view type that this plugin provides. this.registerBasesView(ExampleViewType, { name: "Example", icon: 'lucide-graduation-cap', factory: (controller, containerEl) => { new MyBasesView(controller, containerEl) }, options: () => ([ { // The type of option. 'text' is a text input. type: 'text', // The name displayed in the settings menu. displayName: 'Property separator', // The value saved to the view settings. key: 'separator', // The default value for this option. default: ' - ', }, // ... ]), }); } } ``` -------------------------------- ### Define Custom CSS Styles for Elements Source: https://docs.obsidian.md/Plugins/User+interface/HTML+elements Provides example CSS rules for styling HTML elements within an Obsidian plugin. These styles can be added to a `styles.css` file in the plugin's root directory. ```css .book { border: 1px solid var(--background-modifier-border); padding: 10px; } .book__title { font-weight: 600; } .book__author { color: var(--text-muted); } ``` -------------------------------- ### Handle Custom Markdown Code Blocks with registerMarkdownCodeBlockProcessor Source: https://docs.obsidian.md/Plugins/Editor/Markdown+post+processing Register a processor for specific code block languages (e.g., 'csv') to render them dynamically. This example parses CSV data into an HTML table. ```typescript import { Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { async onload() { this.registerMarkdownCodeBlockProcessor('csv', (source, el, ctx) => { const rows = source.split('\n').filter((row) => row.length > 0); const table = el.createEl('table'); const body = table.createEl('tbody'); for (let i = 0; i < rows.length; i++) { const cols = rows[i].split(','); const row = body.createEl('tr'); for (let j = 0; j < cols.length; j++) { row.createEl('td', { text: cols[j] }); } } }); } } ``` -------------------------------- ### Custom Fuzzy Search Result Rendering Source: https://docs.obsidian.md/Plugins/User+interface/Modals Implement `renderSuggestion` to customize how fuzzy search results are displayed. This example shows how to highlight matches in both the title and author fields of a book item. ```typescript import {FuzzyMatch, FuzzySuggestModal, Notice, renderResults} from "obsidian"; export class ExampleSuggestModal extends FuzzySuggestModal { //return a string representation, so there is something to search getItemText(item: Book): string { return item.title + " " + item.author; } getItems(): Book[] { return ALL_BOOKS; } renderSuggestion(match: FuzzyMatch, el: HTMLElement) { const titleEl = el.createDiv(); renderResults(titleEl, match.item.title, match.match); // Only render the matches in the author name. const authorEl = el.createEl('small'); const offset = -(match.item.title.length + 1); renderResults(authorEl, match.item.author, match.match, offset); } onChooseItem(book: Book, evt: MouseEvent | KeyboardEvent): void { new Notice(`Selected ${book.title}`); } } ``` -------------------------------- ### Use App context in a React component Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Demonstrates how to use the `useApp` custom hook within a React component to access the Obsidian `App` object, for example, to get the vault name. ```typescript import { useApp } from './hooks'; export const ReactView = () => { const { vault } = useApp(); return

{vault.getName()}

; }; ``` -------------------------------- ### Using Obsidian API for Pop-Out Window Compatibility Source: https://docs.obsidian.md/plugins/guides/pop-out-windows Illustrates the correct way to append elements and perform instance checks in pop-out windows using Obsidian's provided APIs like `activeDocument`, `element.doc`, and `element.instanceOf()`. ```typescript let myElement: HTMLElement = ...; // Bad: myElement would be added to the currently focused document, which is not necessarily the one you want activeDocument.body.appendChild(myElement); // Good: This will append myElement to the same window as someElement someElement.doc.body.appendChild(myElement); // This will work correctly in pop-out windows if (myElement.instanceOf(HTMLElement)) { } element.on('click', '.my-css-class', (event) => { // This will work correctly in pop-out windows if (event.instanceOf(MouseEvent)) { } } ``` -------------------------------- ### Get Active Editor Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Access the editor of the active note using app.workspace.activeEditor?.editor. This provides a safe way to get the editor instance, returning undefined if no active editor is available. ```typescript const editor = this.app.workspace.activeEditor?.editor; if (editor) { // ... } ``` -------------------------------- ### Register Settings Tab Source: https://docs.obsidian.md/Plugins/User+interface/Settings Call this method in your plugin's initialization to add a settings tab. Ensure ExampleSettingTab is correctly defined. ```typescript this.addSettingTab(new ExampleSettingTab(this.app, this)); ``` -------------------------------- ### Create a basic View Plugin Source: https://docs.obsidian.md/Plugins/Editor/View+plugins Implement a class extending PluginValue and use ViewPlugin.fromClass() to create a view plugin. The constructor initializes the plugin, update() handles changes, and destroy() cleans up resources. ```typescript import { ViewUpdate, PluginValue, EditorView, ViewPlugin, } from '@codemirror/view'; class ExamplePlugin implements PluginValue { constructor(view: EditorView) { // ... } update(update: ViewUpdate) { // ... } destroy() { // ... } } export const examplePlugin = ViewPlugin.fromClass(ExamplePlugin); ``` -------------------------------- ### Clone Sample Plugin Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Clone the sample Obsidian plugin from its GitHub repository into the plugins directory. Remember to use the URL of your own repository if you've created a fork. ```bash git clone https://github.com/obsidianmd/obsidian-sample-plugin.git ``` -------------------------------- ### Import Notice and Plugin Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Import the necessary classes from the Obsidian API. `Notice` is used for displaying temporary messages to the user. ```typescript import { Notice, Plugin } from 'obsidian'; ``` -------------------------------- ### Get Markdown Files Source: https://docs.obsidian.md/Plugins/Vault Retrieves all Markdown files within the vault. This is useful for operations that specifically target Markdown documents. ```APIDOC ## Get Markdown Files ### Description Retrieves all Markdown files within the vault. This is useful for operations that specifically target Markdown documents. ### Method `app.vault.getMarkdownFiles()` ### Returns - `TFile[]`: An array of TFile objects representing the Markdown files. ``` -------------------------------- ### Plugin Lifecycle with Console Logging Source: https://docs.obsidian.md/Plugins/Getting+started/Anatomy+of+a+plugin Demonstrates how to add console logs to the onload and onunload methods to monitor plugin lifecycle events. ```typescript import { Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { async onload() { console.log('loading plugin') } async onunload() { console.log('unloading plugin') } } ``` -------------------------------- ### Mount a Svelte component in an Obsidian ItemView Source: https://docs.obsidian.md/Plugins/Getting+started/Use+Svelte+in+your+plugin Demonstrates how to mount a Svelte component into an Obsidian ItemView's content element and interact with its methods. ```typescript import { ItemView, WorkspaceLeaf } from 'obsidian'; // Import the Counter Svelte component and the `mount` and `unmount` methods. import Counter from './Counter.svelte'; import { mount, unmount } from 'svelte'; export const VIEW_TYPE_EXAMPLE = 'example-view'; export class ExampleView extends ItemView { // A variable to hold on to the Counter instance mounted in this ItemView. counter: ReturnType | undefined; constructor(leaf: WorkspaceLeaf) { super(leaf); } getViewType() { return VIEW_TYPE_EXAMPLE; } getDisplayText() { return 'Example view'; } async onOpen() { // Attach the Svelte component to the ItemViews content element and provide the needed props. this.counter = mount(Counter, { target: this.contentEl, props: { startCount: 5, } }); // Since the component instance is typed, the exported `increment` method is known to TypeScript. this.counter.increment(); } async onClose() { if (this.counter) { // Remove the Counter from the ItemView. unmount(this.counter); } } } ``` -------------------------------- ### ExampleSettingTab Class Definition Source: https://docs.obsidian.md/Plugins/User+interface/Settings Defines the structure and behavior of your plugin's settings tab. Extend PluginSettingTab and implement the display() method to add settings. ```typescript import ExamplePlugin from './main'; import { App, PluginSettingTab, Setting } from 'obsidian'; export class ExampleSettingTab extends PluginSettingTab { plugin: ExamplePlugin; constructor(app: App, plugin: ExamplePlugin) { super(app, plugin); this.plugin = plugin; } display(): void { let { containerEl } = this; containerEl.empty(); new Setting(containerEl) .setName('Default value') .addText((text) => text .setPlaceholder('Lorem ipsum') .setValue(this.plugin.settings.sampleValue) .onChange(async (value) => { this.plugin.settings.sampleValue = value; await this.plugin.saveSettings(); }) ); } } ``` -------------------------------- ### Add Extra Button to Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Use this to add an extra button to a setting, for example, to reset its value. The button can have custom text and an onClick handler. ```typescript new Setting(containerEl) .setName('Button') .setDesc('With extra button') .addButton(button => button .setButtonText('Click me!') .onClick(() => { /... }) ).addExtraButton(button => button .setIcon('gear') .onClick(() => { //... }) ); ``` -------------------------------- ### Use DOM API or Obsidian helpers Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Prefer using Obsidian's helper functions like `createEl()`, `createDiv()`, and `createSpan()` or the standard DOM API for creating and manipulating HTML elements to ensure security and consistency. ```typescript let containerElement = document.querySelector('.my-container'); // DO THIS let div = containerElement.createDiv({ cls: 'my-class' }); div.createEl('b', { text: 'Your name is: ' }); div.createSpan({ text: name }); ``` -------------------------------- ### Create headings using `setHeading()` Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Use the `Setting` class's `setHeading()` method to create consistent and properly styled headings within your plugin's settings UI, rather than using raw HTML heading elements. ```typescript new Setting(containerEl).setName('your heading title').setHeading(); ``` -------------------------------- ### Save and Load Settings Methods Source: https://docs.obsidian.md/Plugins/User+interface/Settings Implement methods to save and load plugin settings to and from disk using Obsidian's `saveData()` and `loadData()` functions. These methods abstract the data persistence logic. ```typescript export default class ExamplePlugin extends Plugin { // ... async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); } } ``` -------------------------------- ### Updating Status Bar with Interval Timer Source: https://docs.obsidian.md/Plugins/Events Use `window.setInterval()` with `registerInterval()` to repeatedly call a function. This example updates the status bar every second with the current time. ```typescript import { moment, Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { statusBar: HTMLElement; async onload() { this.statusBar = this.addStatusBarItem(); this.updateStatusBar(); this.registerInterval( window.setInterval(() => this.updateStatusBar(), 1000) ); } updateStatusBar() { this.statusBar.setText(moment().format('H:mm:ss')); } } ``` -------------------------------- ### Get Active Markdown View Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Use getActiveViewOfType(MarkdownView) to safely access the active Markdown view. It returns null if the active view is not a MarkdownView or if there is no active view. ```typescript const view = this.app.workspace.getActiveViewOfType(MarkdownView); // getActiveViewOfType will return null if the active view is null, or if it's not a MarkdownView. if (view) { // ... } ``` -------------------------------- ### Create a Color Picker Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Add a color picker to settings, allowing users to select a color. The initial value is set using `setValue`. ```typescript new Setting(containerEl) .setName('Color picker') .addColorPicker(color => color .setValue('#FFFFFF') ); ``` -------------------------------- ### Create a Suggestion Modal Source: https://docs.obsidian.md/Plugins/User+interface/Modals Use `SuggestModal` to present a list of suggestions to the user. Implement `getSuggestions`, `renderSuggestion`, and `onChooseSuggestion` to define the behavior. ```typescript import { App, Notice, SuggestModal } from 'obsidian'; interface Book { title: string; author: string; } const ALL_BOOKS = [ { title: 'How to Take Smart Notes', author: 'Sönke Ahrens', }, { title: 'Thinking, Fast and Slow', author: 'Daniel Kahneman', }, { title: 'Deep Work', author: 'Cal Newport', }, ]; export class ExampleModal extends SuggestModal { // Returns all available suggestions. getSuggestions(query: string): Book[] { return ALL_BOOKS.filter((book) => book.title.toLowerCase().includes(query.toLowerCase()) ); } // Renders each suggestion item. renderSuggestion(book: Book, el: HTMLElement) { el.createEl('div', { text: book.title }); el.createEl('small', { text: book.author }); } // Perform action on the selected suggestion. onChooseSuggestion(book: Book, evt: MouseEvent | KeyboardEvent) { new Notice(`Selected ${book.title}`); } } ``` -------------------------------- ### Create Styled HTML Elements with CSS Classes Source: https://docs.obsidian.md/Plugins/User+interface/HTML+elements Illustrates how to apply CSS classes to newly created HTML elements using the `cls` property in `createEl()`. This allows for custom styling defined in a CSS file. ```typescript const book = containerEl.createEl('div', { cls: 'book' }); book.createEl('div', { text: 'How to Take Smart Notes', cls: 'book__title' }); book.createEl('small', { text: 'Sönke Ahrens', cls: 'book__author' }); ``` -------------------------------- ### Mount React component using createRoot Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Mount a React component to an HTML element, typically `this.contentEl`, using `createRoot` from `react-dom/client`. Ensure to import necessary modules from React and Obsidian. ```typescript import { StrictMode } from 'react'; import { ItemView, WorkspaceLeaf } from 'obsidian'; import { Root, createRoot } from 'react-dom/client'; import { ReactView } from './ReactView'; const VIEW_TYPE_EXAMPLE = 'example-view'; class ExampleView extends ItemView { root: Root | null = null; constructor(leaf: WorkspaceLeaf) { super(leaf); } getViewType() { return VIEW_TYPE_EXAMPLE; } getDisplayText() { return 'Example view'; } async onOpen() { this.root = createRoot(this.contentEl); this.root.render( , , ); } async onClose() { this.root?.unmount(); } } ``` -------------------------------- ### Registering a File Creation Event Source: https://docs.obsidian.md/Plugins/Events Subscribe to file creation events using `app.vault.on('create')`. Ensure event handlers are detached when the plugin unloads by using `registerEvent()`. ```typescript import { Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { async onload() { this.registerEvent(this.app.vault.on('create', () => { console.log('a new file has entered the arena') })); } } ``` -------------------------------- ### Create a Modal with User Input Source: https://docs.obsidian.md/Plugins/User+interface/Modals Create modals that accept user input by adding `Setting` elements. The `onSubmit` callback receives the user's input when the modal is closed. ```typescript import { App, Modal, Setting } from 'obsidian'; export class ExampleModal extends Modal { constructor(app: App, onSubmit: (result: string) => void) { super(app); this.setTitle('What\'s your name?'); let name = ''; new Setting(this.contentEl) .setName('Name') .addText((text) => text.onChange((value) => { name = value; })); new Setting(this.contentEl) .addButton((btn) => btn .setButtonText('Submit') .setCta() .onClick(() => { this.close(); onSubmit(name); })); } } ``` -------------------------------- ### Create a Basic Modal Source: https://docs.obsidian.md/Plugins/User+interface/Modals Extend the `Modal` class to create a custom modal. Use the constructor to set the modal's title and content. The `app` instance is required. ```typescript import { App, Modal } from 'obsidian'; export class ExampleModal extends Modal { constructor(app: App) { super(app); this.setContent('Look at me, I\'m a modal! 👀') } } ``` -------------------------------- ### Add Moment Date Format Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Provides a date formatting input that uses moment.js. It includes a live preview and a link to the format reference. ```typescript const dateDesc = document.createDocumentFragment(); dateDesc.appendText('For a list of all available tokens, see the '); dateDesc.createEl('a', { text: 'format reference', attr: { href: 'https://momentjs.com/docs/#/displaying/format/', target: '_blank' } }); dateDesc.createEl('br'); dateDesc.appendText('Your current syntax looks like this: '); const dateSampleEl = dateDesc.createEl('b', 'u-pop'); new Setting(containerEl) .setName('Date format') .setDesc(dateDesc) .addMomentFormat(momentFormat => momentFormat .setValue(this.plugin.settings.dateFormat) .setSampleEl(dateSampleEl) .setDefaultFormat('MMMM dd, yyyy') .onChange(async (value) => { this.plugin.settings.dateFormat = value; await this.plugin.saveSettings(); })); ``` -------------------------------- ### Register vault event listener after layout is ready Source: https://docs.obsidian.md/plugins/guides/load-time To avoid issues with Obsidian's vault initialization, register vault event listeners within an `onLayoutReady` callback. This ensures the workspace is fully initialized before the listener starts reacting to events. ```typescript class MyPlugin extends Plugin { onload(app: App) { super(app); this.registerEvent(this.app.vault.on('create', this.onCreate, this)); } onCreate() { if (!this.app.workspace.layoutReady) { // Workspace is still loading, do nothing return; } // ... } } ``` ```typescript class MyPlugin extends Plugin { onload(app: App) { super(app); this.app.workspace.onLayoutReady(() => { this.registerEvent(this.app.vault.on('create', this.onCreate, this)); }); } onCreate() { // ... } } ``` -------------------------------- ### Create a Dropdown Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Add a dropdown menu to a setting, allowing users to select from predefined options. The selected value is saved and the settings are updated. ```typescript new Setting(containerEl) .setName('Dropdown') .addDropdown((dropdown) => dropdown .addOption('1', 'Option 1') .addOption('2', 'Option 2') .addOption('3', 'Option 3') .setValue(this.plugin.settings.mySetting) .onChange(async (value) => { this.plugin.settings.mySetting = value; await this.plugin.saveSettings(); }) ); ``` -------------------------------- ### Add Ribbon Icon and Action Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Add a custom icon to the Obsidian ribbon. When clicked, this icon will display a 'Hello, world!' notice to the user. ```typescript this.addRibbonIcon('dice', 'Greet', () => { new Notice('Hello, world!'); }); ``` -------------------------------- ### Create GitHub Actions Release Workflow Source: https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions This YAML file defines a GitHub Actions workflow that triggers on tag pushes. It checks out the code, sets up Node.js, builds the plugin, and creates a draft GitHub release with the built assets. ```yaml name: Release Obsidian plugin on: push: tags: - "*" jobs: build: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: "18.x" - name: Build plugin run: | npm install npm run build - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tag="${GITHUB_REF#refs/tags/}" gh release create "$tag" \ --title="$tag" \ --draft \ main.js manifest.json styles.css ``` -------------------------------- ### Import Svelte build tools in esbuild.config.mjs Source: https://docs.obsidian.md/Plugins/Getting+started/Use+Svelte+in+your+plugin Add necessary imports for esbuild-svelte and svelte-preprocess to your esbuild configuration file. ```javascript import esbuildSvelte from 'esbuild-svelte'; import { sveltePreprocess } from 'svelte-preprocess'; ``` -------------------------------- ### Basic Plugin Settings Structure Source: https://docs.obsidian.md/Plugins/User+interface/Settings This snippet shows the fundamental structure for a plugin that includes settings. It defines the settings interface, default settings, and the methods for loading and saving them. ```typescript import { Plugin } from 'obsidian'; import { ExampleSettingTab } from './settings'; interface ExamplePluginSettings { sampleValue: string; } const DEFAULT_SETTINGS: Partial = { sampleValue: 'Lorem ipsum', }; export default class ExamplePlugin extends Plugin { settings: ExamplePluginSettings; async onload() { await this.loadSettings(); this.addSettingTab(new ExampleSettingTab(this.app, this)); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); } } ``` -------------------------------- ### Add Setting Heading Source: https://docs.obsidian.md/Plugins/User+interface/Settings Use the setHeading() method to create a section header within your settings tab, useful for organizing many settings. ```typescript new Setting(containerEl).setName("Defaults").setHeading(); ``` -------------------------------- ### Create React App context Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Create a React Context to provide the Obsidian `App` object globally to your React components. This avoids prop drilling. ```typescript import { createContext } from 'react'; import { App } from 'obsidian'; export const AppContext = createContext(undefined); ``` -------------------------------- ### Create Nested HTML Elements Source: https://docs.obsidian.md/Plugins/User+interface/HTML+elements Shows how to create a `
` element and then add nested `
` and `` elements within it, assigning text content to each. ```typescript const book = containerEl.createEl('div'); book.createEl('div', { text: 'How to Take Smart Notes' }); book.createEl('small', { text: 'Sönke Ahrens' }); ``` -------------------------------- ### Create Git Tag for Release Source: https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions Create an annotated Git tag for a new release, ensuring the tag name matches the version in `manifest.json`. Push the tag to the remote repository to trigger the GitHub Actions workflow. ```bash git tag -a 1.0.1 -m "1.0.1" git push origin 1.0.1 ``` -------------------------------- ### Load Settings on Plugin Load Source: https://docs.obsidian.md/Plugins/User+interface/Settings Ensure that your plugin's settings are loaded when the plugin is initialized by calling `loadSettings()` within the `onload()` method. ```typescript async onload() { await this.loadSettings(); // ... } ``` -------------------------------- ### Emulate Mobile Device on Desktop Source: https://docs.obsidian.md/Plugins/Getting+started/Mobile+development Enable mobile emulation in Obsidian Developer Tools to test your plugin on a simulated mobile environment. Disable it to return to the desktop view. ```javascript this.app.emulateMobile(true); ``` ```javascript this.app.emulateMobile(false); ``` ```javascript this.app.emulateMobile(!this.app.isMobile); ``` -------------------------------- ### Open Context Menu at Mouse Event - TypeScript Source: https://docs.obsidian.md/Plugins/User+interface/Context+menus Use `Menu.showAtMouseEvent()` to display a custom context menu at the user's click location. Ensure the `Menu` class is imported from 'obsidian'. ```typescript import { Menu, Notice, Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { async onload() { this.addRibbonIcon('dice', 'Open menu', (event) => { const menu = new Menu(); menu.addItem((item) => item .setTitle('Copy') .setIcon('documents') .onClick(() => { new Notice('Copied'); }) ); menu.addItem((item) => item .setTitle('Paste') .setIcon('paste') .onClick(() => { new Notice('Pasted'); }) ); menu.showAtMouseEvent(event); }); } } ``` -------------------------------- ### List all Markdown files in a Vault Source: https://docs.obsidian.md/Plugins/Vault Recursively prints the paths of all Markdown files in the vault. Use `getFiles()` to list all file types. ```typescript const files = this.app.vault.getMarkdownFiles() for (let i = 0; i < files.length; i++) { console.log(files[i].path); } ``` -------------------------------- ### Avoid Iterating Files: Use Vault Methods Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Instead of iterating through all files, use Vault.getFileByPath, Vault.getFolderByPath, or Vault.getAbstractFileByPath for efficient file retrieval. This is crucial for performance with large vaults. ```typescript this.app.vault.getFiles().find(file => file.path === filePath); ``` ```typescript const filePath = 'folder/file.md'; // if you want to get a file const file = this.app.vault.getFileByPath(filePath); ``` ```typescript const folderPath = 'folder'; // or if you want to get a folder const folder = this.app.vault.getFolderByPath(folderPath); ``` ```typescript const abstractFile = this.app.vault.getAbstractFileByPath(filePath); if (file instanceof TFile) { // it's a file } if (file instanceof TFolder) { // it's a folder } ``` -------------------------------- ### Navigate to Obsidian Plugins Directory Source: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin Change directory to the .obsidian/plugins folder within your vault. This is where Obsidian looks for community plugins. ```bash cd path/to/vault mkdir .obsidian/plugins cd .obsidian/plugins ``` -------------------------------- ### Register a Basic Command Source: https://docs.obsidian.md/Plugins/User+interface/Commands Register a new command by calling `addCommand()` within the plugin's `onload()` method. This command can be invoked from the Command Palette or via a hotkey. ```typescript import { Plugin } from 'obsidian'; export default class ExamplePlugin extends Plugin { async onload() { this.addCommand({ id: 'print-greeting-to-console', name: 'Print greeting to console', callback: () => { console.log('Hey, you!'); }, }); } } ``` -------------------------------- ### Create a Progress Bar Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Display a progress bar to indicate task progress or a quota. The `setValue` method takes a number representing the percentage. ```typescript new Setting(containerEl) .setName('Progress bar') .setDesc('It\'s 50% done') .addProgressBar(bar => bar.setValue(50)); ``` -------------------------------- ### Provide Default Settings Source: https://docs.obsidian.md/Plugins/User+interface/Settings Use `Object.assign()` with `DEFAULT_SETTINGS` and the data loaded from `loadData()` to provide default values for settings when the plugin is first enabled or when certain settings are not yet configured. ```typescript Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) ``` ```typescript const DEFAULT_SETTINGS: Partial = { sampleValue: 'Lorem ipsum', }; ``` -------------------------------- ### Register an Editor Command Source: https://docs.obsidian.md/Plugins/User+interface/Commands Utilize `editorCallback()` to create commands that require access to the active editor instance. The callback receives the `editor` and `view` as arguments. ```typescript this.addCommand({ id: 'example-command', name: 'Example command', editorCallback: (editor: Editor, view: MarkdownView) => { const sel = editor.getSelection() console.log(`You have selected: ${sel}`); }, }) ``` -------------------------------- ### Add svelte-check script to package.json Source: https://docs.obsidian.md/Plugins/Getting+started/Use+Svelte+in+your+plugin Include a script in your package.json to easily run svelte-check for type checking your Svelte components. ```json { // ... "scripts": { // ... "svelte-check": "svelte-check --tsconfig tsconfig.json" } } ``` -------------------------------- ### Register and Activate Custom View in Plugin Source: https://docs.obsidian.md/Plugins/User+interface/Views Register your custom view using registerView() in the plugin's onload method. Add a ribbon icon to activate the view using a custom activateView method. ```typescript import { Plugin, WorkspaceLeaf } 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) ); this.addRibbonIcon('dice', 'Activate view', () => { this.activateView(); }); } async onunload() { } async activateView() { const { workspace } = this.app; let leaf: WorkspaceLeaf | null = null; const leaves = workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE); if (leaves.length > 0) { // A leaf with our view already exists, use that leaf = leaves[0]; } else { // Our view could not be found in the workspace, create a new leaf // in the right sidebar for it leaf = workspace.getRightLeaf(false); await leaf.setViewState({ type: VIEW_TYPE_EXAMPLE, active: true }); } // "Reveal" the leaf in case it is in a collapsed sidebar workspace.revealLeaf(leaf); } } ``` -------------------------------- ### Create a Fuzzy Suggestion Modal Source: https://docs.obsidian.md/Plugins/User+interface/Modals Leverage `FuzzySuggestModal` for out-of-the-box fuzzy string matching. Implement `getItems`, `getItemText`, and `onChooseItem` to customize. ```typescript import {FuzzySuggestModal, Notice} from "obsidian"; export class ExampleSuggestModal extends FuzzySuggestModal { getItems(): Book[] { return ALL_BOOKS; } getItemText(book: Book): string { return book.title; } onChooseItem(book: Book, evt: MouseEvent | KeyboardEvent) { new Notice(`Selected ${book.title}`); } } ``` -------------------------------- ### Create a basic React component Source: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin Define a simple React functional component that can be imported and used within your plugin. ```typescript export const ReactView = () => { return

Hello, React!

; }; ``` -------------------------------- ### Create a Toggle Setting Source: https://docs.obsidian.md/Plugins/User+interface/Settings Implement a toggle switch for a setting. This is useful for boolean options. It saves the new value and re-renders the settings display. ```typescript new Setting(containerEl) .setName('Toggle') .addToggle(toggle => toggle .setValue(this.plugin.settings.localServer) .onChange(async (value) => { this.plugin.settings.localServer = value; await this.plugin.saveSettings(); this.display(); }) ); ``` -------------------------------- ### Define Settings Interface Source: https://docs.obsidian.md/Plugins/User+interface/Settings Define the structure of your plugin's settings using a TypeScript interface. This allows for type checking and clear definition of configurable options. ```typescript interface ExamplePluginSettings { sampleValue: string; } export default class ExamplePlugin extends Plugin { settings: ExamplePluginSettings; // ... } ``` -------------------------------- ### Reveal and access a deferred view Source: https://docs.obsidian.md/plugins/guides/defer-views To access a custom view that might be deferred, first reveal its leaf to ensure it's visible and fully loaded, then perform the `instanceof` check. ```typescript let leaf = workspace.getLeavesOfType('my-view').first(); if (leaf) { await workspace.revealLeaf(leaf); // Ensure the view is visible, `await` it to make sure the view is fully loaded if (leaf.view instanceof MyCustomView) { let view = leaf.view; // You now have your CustomView } } ``` -------------------------------- ### Global Variable Issues in Pop-Out Windows Source: https://docs.obsidian.md/plugins/guides/pop-out-windows Demonstrates common pitfalls when using global variables like `document` and `HTMLElement` in pop-out windows. These globals are not shared across windows, leading to unexpected behavior. ```typescript let myElement: HTMLElement = ...; // This will always append to the main window document.body.appendChild(myElement); // This will actually be false if element is in a pop-out window if (myElement instanceof HTMLElement) { } element.on('click', '.my-css-class', (event) => { // This will be false if the event is triggered in a pop-out window if (event instanceof MouseEvent) { } } ``` -------------------------------- ### Read File Content Source: https://docs.obsidian.md/Plugins/Vault Reads the content of a file. `cachedRead()` is recommended for displaying content to avoid redundant disk reads, while `read()` is suitable for content that will be modified and rewritten. ```APIDOC ## Read File Content ### Description Reads the content of a file. `cachedRead()` is recommended for displaying content to avoid redundant disk reads, while `read()` is suitable for content that will be modified and rewritten. ### Methods - `app.vault.cachedRead(file: TFile): Promise` - `app.vault.read(file: TFile): Promise` ### Parameters - **file** (`TFile`): The file to read. ### Returns - `Promise`: A promise that resolves with the content of the file. ``` -------------------------------- ### Use `this.app` instead of global `app` Source: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines Always use the plugin instance's `this.app` reference instead of the global `app` object to avoid potential issues and future incompatibilities. ```typescript import { Plugin } from 'obsidian'; export default class MyPlugin extends Plugin { async onload() { // DON'T DO THIS // console.log(window.app); // DO THIS console.log(this.app); } } ``` -------------------------------- ### Commit Release Workflow File Source: https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions Stage and commit the newly created release workflow file to your Git repository. ```bash git add .github/workflows/release.yml git commit -m "Add release workflow" git push origin main ```