DEV Community

Cover image for Journey: Developing an Obsidian Plugin Part 1 - Getting Started
Bjarne Rentz
Bjarne Rentz

Posted on

Journey: Developing an Obsidian Plugin Part 1 - Getting Started

First of all, to set the right expectations, this is not a tutorial on how to develop Obsidian plugins. It's just a write-up of my journey developing and publishing an Obsidian plugin. I will write about my problems and my lessons learned, but not a step-by-step tutorial. The first part we will cover the idea, getting started with the development and submitting the MVP.

The Idea

As you may have probably noticed, everyone is talking about AI. So why not jump on the hype train as well? For university (and everything else) I use the fantastic note-taking tool Obsidian. Once in a lecture I asked myself, why don't use ChatGPT or Google Gemini for this? Sure, the details are probably pretty bad on some topics, but the overall structure is already there. So I started with Google Gemini and the simple prompt Generate me an Obsidian Markdown Note on: >>NoteTitel<< and copied the result into Obsidian.

Depending on the topic, the notes were acceptable and needed only minor changes. I asked myself:

Wouldn't it be easier to just create the note in Obsidian, set the title and with the help of a plugin and AI, use a command to generate the Note.

That's the simple, yet handy idea behind my plugin Gemini Generator. For a better understanding the GIF shows the workflow with Gemini Generator.

Starting the Project

At first I was a bit overwhelmed not knowing where to start. However, although it only shows the basics and not many examples of plugin functionality, the Obsidian tutorial and the sample plugin, which provides the basic structure of a plugin, are a good place to start. It's often easier to start with something simple and learn as you go, rather than planning and researching everything in advance. Sure, it's always important to plan a basic path for learning something new, but leave some room for learning along the way!

Starting with forking the sample project and renaming all relevant class names, the first goals were to find the appropriate command type and how to get the title of the currently open note. The first task was easily solved with the EditorCommand, as it provides an instance of the Editor for writing the response and is only available when an Editor-tab is open and active - exactly what we needed.

Getting the title of the currently active note was not so easy. My first guess was to use the mentioned instance of the Editor available through the EditorCommand. Unfortunately, I found no useful way to get the desired title. After searching the documentation (that has room to improve) and trying different things out, I ended up with plugin.app.workspace.getActiveFile()?.basename;. I'm not sure, weather its the best way or not. So please let me know if there is a better way!

Well, we have a command, the notes title, but the Gemini response is missing. Luckily, Google provides an easy to use NPM package and the first basic command implementation was done:

this.addCommand({
    id: "generate-note",
    name: "Generate Note with Gemini",
    editorCallback: async (editor: Editor) => {
        const title = this.app.workspace.getActiveFile()?.basename;
        if (!title) {
            new Notice("Could not get filename", 1500);
        }

        editor.setCursor(editor.lastLine());

        const prompt = `Write a me an Obsidian Markdown Note without the Title on:${title} `;
        const result = await model.generateContentStream(prompt);

        for await (const chunk of result.stream) {
            const chunkText = chunk.text();
            editor.replaceRange(chunkText, editor.getCursor());
            editor.setCursor(editor.lastLine());
        }
            new Notice("✅ Finished", 1500);
    },
});

Enter fullscreen mode Exit fullscreen mode

As you can see, I wanted to use the stream-based response for a more responsive note generation. To do this, we need to append the current chunk to the end of the editor using replaceRangeand setCursor. Just using replaceRange did not work, because it keeps the cursor at the original position.

Alright, the editor command is up and running - only task left is to publish? It would be possible, but as I like to automate things, I wanted to add a CI pipeline before publishing the first version. The pipeline is triggered by a git tag and performs the two simple steps:

  1. Build the plugin (npm ciand npm run build)
  2. Create a draft release using the Github CLI.

Thus, for a new release, I can use the recommended command npm version (patch|minor|major), do a git push --tags, verify the release and publish it.

Submitting the Plugin

My original goal was to submit the plugin as soon as possible and provide new features as updates. However, during development I came up with new ideas: custom prompts, being able to change the default prompt, response processing, etc.
However, I decided to postpone them until after the initial release in order to gain experience in submitting and managing the first public release.

Hence, it was time to submit the first version of Gemini Generator. As mentioned above, a Github release is already created through the pipeline. However, in order for the plugin to be found by Obsidian, you need to submit the plugin for review and approval, as described in this guide. During this process, my pull request encountered a strange error where the pipeline performing some automated checks failed to checkout the pull request. I asked for help on the official Obsidian Discord server (which I can strongly recommend!), but the suggested fixes did not work. In the end, I had to close the PR and create a new one. After a few days, I got the approval and the first version of Gemini Generator was publicly available!

In part two we will improve the architecture of the plugin, add basic error handling and implement a first basic version of automatic response processing. So, if you're interested, don't forget to follow.

If you want to have a look at source code as well, you can find the Github Repo here.

Top comments (0)