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:
- Does this really need a plugin?
- Does a plugin already exist that does what you need?
- Is a plugin the right layer of abstraction?
- Maybe you just want to interface through the Obsidian REST API? Well, you made your mind up. Then let's head right in...
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.
- vault on modify
metadataCache.on('changed')
// 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
this.app.setting.open()
this.app.setting.openTabById('plugin-manifest-id')
obsidian api DataView