A more streamlined development workflow for Obsidian plugins
Lukas Bach
Posted on April 28, 2023
I recently started to play around with Obsidian as note taking app, and am a very big fan of the large availability of community plugins and the open architecture that really allows lots of customizability through plugins. I wanted bridge the gaps of features that I was missing from Obsidian so far, and built two of my own plugins that recently got published in the Obsidian app store: Obsidian Code Files, a plugin for editing text files with Monaco Editor, the editor instance that powers VSCode, and Obsidian File Order, a plugin for reordering files in the file tree.
In this short article, I wanted to share some learnings that I gained from building these two plugins, and the setup I ended up with to automate the majority of the development and releasing process, which is what I find somewhat complicated and unintuitive by itself.
The usual plugin development process
Obsidian provides a GitHub template repo that can be used as starter to build a plugin. It contains a scaffold of what is needed to get started, and some documentation on what needs to be done to develop and release plugin versions, but the entire process feels very manual and has many steps involved.
Let's start by looking into how plugins are distributed and installed: A plugin consists of a main.js
and a manifest.json
file, and optionally also a styles.css
file. When installed, they are stored in the .obsidian/plugins/pluginname
folder within your vault. To release a new version, a new GitHub release needs to be published, and the plugin files need to be attached to the release. The version in the release needs to be bumped and match the version in the manifest.json
file. A comprehensive documentation is available in the readme of the template repo.
The template repo contains a custom version script which automatically syncs the version in the package.json
file with the version in the manifest.json
file, so bumping the version through npm version
will also bump the manifest file. It also contains an esbuild setup to build and bundle the TypeScript source code into a main.js
bundle file. However, drafting the GitHub release, making sure it has the right name (since the plugin registry of Obsidian is actually sensitive against the release name), and uploading the files is still a manual process. This is tedious, but can also lead to human failure, for example if you forget to build the plugin beforehand and thus publish an old version, or use an incorrect version.
Also development is inconvenient because there is no perfect way to get the plugin into Obsidian while you develop it. The intended way is to place your entire repo into the plugins folder of your Obsidian vault, so that Obsidian sees the built JS file and directly loads the plugin from there. However, being restricted in where to place the repo is inconvenient for some setups (for example, I have my vault placed in a OneDrive folder, but I don't want to place a repository with a gigantic node_modules
folder somewhere where it will be synced with a cloud), and also you need to reload the Obsidian UI everytime you make a change.
Improving the development process
To make development easier, I've opted into using the package obsidian-plugin-cli
. It is an unofficial utility tool not maintained by Obsidian, and is still marked as Alpha software with the note that it will bring breaking changes in the future. However, I found that it improved the development process greatly, was really easy to use and didn't really have any issues that I encountered.
It replaces the esbuild setup in your plugin project, which previously just build your TypeScript source into a JS bundle, with a more elaborate setup that does many things for you. It wraps an esbuild setup so you don't have to define your own, but more importantly, provides a "watching" dev script that automatically installs your devloped plugin into a local vault of your choosing, and automatically rebuilds it when changes are detected. That means you can place the source code of your plugin anywhere on your machine, and just install your plugin into any arbitrary vault.
That means, that the scripts
{
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
}
}
becomes
{
"scripts": {
"dev": "obsidian-plugin dev --with-stylesheet src/styles.css src/main.ts",
"build": "obsidian-plugin build --with-stylesheet src/styles.css src/main.ts"
}
}
The library does the rest for you. It also offers to install a hot-reload plugin into Obsidian, which will automatically to a light reload in Obsidian when you change something locally in your plugin, which is much faster than the full reload you would normally have to do, and also more convenient since you don't need to manually trigger a reload in Obsidian.
Improving the releasing process
This still left the release process of Obsidian an unconvenient labor that I wanted to improve for my setup. For that, I modified a publishing CLI tool that I worked on at an earlier point to be compatible with the release requirements of Obsidian: publish-fast. It is a simple npm release wrapper, that supports configuration to automatically run scripts like building or linting beforehands, which can bump the package version, manage changelogs and also create a github release and upload asset files.
publish-fast
can be configured through a dedicated file, or a publish
object in the package.json
file. The necessary setup was installing it through npm i publish-fast --save-dev
and adding the following to the package.json
file:
{
"scripts": {
"release": "publish-fast",
"postversion": "node version-bump.mjs"
},
"publish": {
"preScripts": "lint,build",
"skipPublish": true,
"releaseAssets": "dist/*",
"noVersionPrefix": true
}
}
This make the publish process run the lint- and build-script prior to release, skips publishing the package to the NPM registry and uploads the files in the dist
folder to the created github release. The remaining defaults for the tool do the rest, i.e. the tool automatically creates a GitHub release and bumps the version by default.
I still needed the custom script that syncs the version from package.json
to manifest.json
, but moved the execution of that script to a postversion
script, so it would be automatically called by NPM after the version bump. For completeness, here is the version bump script that is provided by the Obsidian template repo:
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
This is pretty much it, running npm run release
now automatically bumps the version, builds your assets, and creates a new release on GitHub to which it uploads your assets. If you want to release a new version, you just need to run that script. You could even set it up as a CI pipeline in GitHub actions to run completely automated.
Summary
Obsidian is a pretty cool tool for note-taking and working with markdown notes, and the big community and easy ways to improve the Obsidian experience through custom plugins is really nice. I tried to improve some aspects of plugin development and automate some of the more tedious aspects of it. I found obsidian-plugin-cli
, which made development much more convenient, and adopted my own tool publish-fast
to implement an automated release process that makes it easier to move fast in developing Obsidian plugins.
Posted on April 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.