Obsidian Plugin Development

I store my second brain in Obsidian but as I settled into it, I found that I need some extras to make it my universal center of knowledge.

Why?

I wanted to organize my output pipelines and did not find a proper tool to export structured notes from my collection. I needed to create [Obsidian Bulk Exporter](org/2-areas/3-profession/obsidian/Obsidian Bulk Exporter) plugin for automate my publishing pipeline and I was planning to make a [Bulk Metadata Editor](x archive/2023-vanlife-org/archive/obsidian/Bulk Metadata Editor) to edit and tag my older files so they can be exported. (this I am no longer sure if I'll ever do...)

In this process I wanted to help the community and document my findings about how to develop an Obsidian Plugin.

Before you get down developing a plugin, I highly recommend to ask the following questions from yourself:

Setup

Open debugger with Ctrl + Shift + i

Start from scratch

If you want to start from scratch, clone the example repo, then have a look around.

Start running it with npm run dev

Docs

Unofficial Obsidian Plugin Docs

Official Github: Obsidian Sample Plugin

Icons: Basic Obsidian Icon Search

Publishing

Do a self review before you publish!

Dataview plugin has an API!

API

Do not use this.app - ever, use the plugin instance's plugin.app instead.

Opening a file:

fileItemContainer.addEventListener('click', ()=>{
const file = plugin.app.metadataCache.getFirstLinkpathDest(tree.children[fileName], "");
// This is marked deprecated, but found only this.
const leaf = this.app.workspace.getUnpinnedLeaf();
// Something like this could work too:
// const leaf =
// this.app.workspace.getLeavesOfType("markdown")[0]
leaf.openFile(file, {active: true})
})```

### File Explorer
How to highlight (reveal) a file on the left panel file tree component:

`
``ts
export function revealInFolder(path: string) {
const fileExplorer =
plugin.app.internalPlugins
.getPluginById('file-explorer').instance

if (fileExplorer) {
const fileObject =
plugin.app.vault.getAbstractFileByPath(path)

fileExplorer.revealInFolder(fileObject)
}
}
Iterate over every file-explorer instance

Nice Monkey Patching showing how to change the file-explorer internal plugin to show custom elements in it. Obsidian File Color

const fileExplorers = this.plugin.app.workspace.getLeavesOfType("file-explorer");

// Iterate over all the file explorers present
fileExplorers.forEach((fileExplorer) => {

// @ts-ignore: the type of this is obstructed, as it's an internal plugin.
const fileExplorerFileItemsMap = fileExplorer.view.fileItems;
// fileExplorerItems will be a path: element store, that

Object.entries(fileExplorerFileItemsMap).forEach(
([path, fileItem]) => {

// @ts-ignore: so as fileItem: it's an internal type the file explorer uses.
const fileItemHTMLElement = fileItem.selfEl as HTMLElement;
// This you can append, modify and it will be instantly visible on the sidebar

More ideas for this style of monkey-patching is in Obsidian Icon Folder plugin and File Color plugin

DataView

Docs GitHub Pretty nifty plugin for writing queries and generate dynamic content based on front-matter-data.

Indexes

It already does the heavy lifting, as

this.app.plugins.plugins.dataview.api.index
this.app.plugins.plugins.dataview.api.index.tags.delegate.invMap

DataView does not support multi-word tags, so a solution to that is using queries with and, so if you have a tag like raspberry pi you will have two indexes in it: raspberry and pi. You can search for this like this:

FROM #raspberry AND #pi

On File Change

There are multiple way to subscribe to file changes.

// Where 'this' is the plugin's this.
this.registerEvent(
this.app.metadataCache.on("changed", (tFile) => {
console.warn("my change", tFile);
})
);

// When using dataview, metadataCache is also called right after with the same attributes
this.registerEvent(
this.app.metadataCache.on(
"dataview:metadata-change",
(type, file, oldPath?) => {
console.warn('this file is the same as the above', file)
}
})
);

Open a plugin's settings panel directly

ref

this.app.setting.open()
this.app.setting.openTabById('plugin-manifest-id')