Automating the Loop: The Obsidian Follow-up Plugin
As a developer at heart, I’m always looking for ways to make my daily work a little easier. Whether it’s home automation or organizing my notes, if I have to do the same boring thing more than twice, I want to automate it.
Recently, I noticed a problem with how I was managing tasks in Obsidian that was driving me crazy.
The “Aha!” Moment
It often goes like this: I have a task, say, “Call the electrician.” I make the call, get the info I need, and… well, the job isn’t done. Now I need to “Send usage report to the electrician” or “Schedule the appointment.”
Marking the original task as complete felt good, but immediately creating a new task, copying the info, and setting a reminder felt like a huge hassle. It was just enough extra work that sometimes I wouldn’t do it, and the follow-up would get forgotten.
I wanted a system where I could just “check the box” and trust that the system would remember to remind me later.
The Solution: Automating the Loop
I built the Obsidian Follow-up Plugin. The idea is simple: use a tag to tell the system, “This isn’t the end of the story.”
How it works
- Tag it: Add
#followupto any task.- [ ] Call the electrician #followup
- Check it: When the task is done, just check the box.
- [x] Call the electrician #followup
- Magic happens: The plugin instantly catches that change and creates a new task right below it.
- [x] Call the electrician #followup 🆔 9s2la
- [ ] #todo Follow up on: Call the electrician ⛔ 9s2la 📅 2026-01-22 #followup
It automatically dates the new task for tomorrow and keeps the #followup tag so the chain continues if needed. It’s a real “set and forget” loop.
Under the Hood
For those curious about the technical side, this is built using TypeScript, which is standard for Obsidian plugins.
The core logic revolves around a simple event listener. We hook into the vault’s modify event, which fires whenever a file is changed.
this.registerEvent(
this.app.vault.on('modify', async (file) => {
if (file instanceof TFile && file.extension === 'md') {
await this.scanAndProcess(file);
}
})
);
The Logic
The real work happens in scanAndProcess. We read the file and look for lines that match a specific condition:
- Is it a completed task? (
- [x]) - Does it have the
#followuptag? - Crucially: Does it lack an ID?
That last point is key. To prevent infinite loops or double tasks, we add a unique ID (🆔) to the completed task. If a task already has one, we ignore it.
// Regex to check for completed tasks with #followup
const isCompletedTask = /^(\s*)[-*]\s+\[[xX]\]/.test(line);
const hasFollowupTag = line.includes('#followup');
const hasId = line.includes('🆔');
When we find a match, we generate a random 5-character ID, update the original line to include it (locking it from being processed again), and add the new task line right after it.
We also use a “blocker” ID (⛔) in the new task that points back to the original. This creates a permanent link between the two, which could be useful for more complex stuff down the road.
Built with AntiGravity
Here is the kicker: I didn’t write most of this code by hand, as I don’t have the time anymore to code from scratch. I built this plugin using the AntiGravity IDE.
As an engineer, I know how to write TypeScript, but the hassle of setting up the environment, remembering the specific Obsidian commands, and handling the setup files often stops me before I start. AntiGravity allowed me to skip the setup and jump straight to the logic.
The Development Process
My role shifted from “coder” to “planner.” I described what I wanted, and the IDE handled the how.
Here are the types of prompts I used to guide the development:
1. The “Scaffold” Prompt:
“Create a new Obsidian plugin structure. I want to listen for file modifications and detect when a task line with specific tags is changed.”
2. The “Logic” Prompt:
“Write a function that parses the current file. If it finds a line that starts with
- [x]and contains#followup, but does NOT contain a unicode ID like🆔, I want you to append a random 5-character ID to that line.”
3. The “Refinement” Prompt:
“Now, immediately after that modified line, insert a new task line. It should have the same indentation, be unchecked
- [ ], start with#todo, and contain a blocker link⛔ {id}referencing the original task.”
By going back and forth with these prompts, I could focus on the tricky parts (like “Wait, what if I check it twice?” or “Make sure it keeps the list structure”) without getting stuck on code errors.
Why this matters
This plugin fits perfectly into my philosophy of “lazy productivity.” By removing the manual step of creating the next task, I remove the excuse to be disorganized. The system captures the intent – “this needs follow-up” – and ensures it appears on my radar tomorrow.
If you’re interested in trying it out or looking at the code, you can find it on my GitHub.
https://github.com/hasnainmirza27/obsidian-followup-plugin
Did I get an AI to write most of this blog, yes yes i did 🙂