The Obsidian Plugin Bug That Hides Your Upgrade Button
5 min read
I was doing unrelated work on one of my Obsidian plugins when I noticed something. A user had hit their free-tier limit. The plugin showed a small notice saying the limit was reached. That was it. No link, no button, nothing to click.
I checked the other six plugins. Same pattern in all of them. Seven plugins, each with a working offline licensing system, and not one of them showed a way to upgrade at the moment a user actually needed it.
That’s the real story behind Obsidian plugin monetization for a lot of small indie builders, I suspect — not pricing, not distribution, just a door nobody remembered to put a handle on. The licensing logic worked. The free tier worked. The Pro tier worked. The connection between them didn’t exist.
What the free-tier limit actually looked like
Before the fix, hitting the free-tier limit triggered a plain Obsidian Notice — a small toast that fades after a few seconds. It said the limit was reached. It didn’t say what to do about it. Unless you already knew the plugin had a Pro tier, there was no way to find out from inside that moment. You’d have to go dig through settings, or GitHub, or the Obsidian Community listing — none of which the notice pointed to.
After the fix, the same moment opens a modal instead. It explains the limit, shows what Pro unlocks, and has a button straight into the plugin’s settings tab, where the license key field lives. You don’t need to know anything about how the plugin makes money to find the door. It’s just there now.
Two more bugs I found once I started looking
Going plugin by plugin to check this one thing surfaced two more problems worth flagging on their own, because they’re not monetization advice. They’re closer to a warning about what offline licensing code quietly drifts into if nobody revisits it.
One plugin’s free-tier copy said the usage limit was a one-time allowance. The counter logic underneath reset it every 30 days instead. Nobody had touched that counter since it was first written, and somewhere along the way the text describing it stopped matching what it did. Both the code and the copy looked fine in isolation. They’d just quietly stopped agreeing with each other.
The other bug was a race condition. The usage counter incremented on read in some code paths and on write in others, depending on which feature triggered it. Under normal single-user, single-session use this barely mattered — but it meant the “remaining uses” number could be off by one, and enforcement could let one extra action through before blocking. Not a security hole. Just a small gap between what the counter said and what it actually counted.
Neither bug shows up in a quick manual test. They show up when you go back and ask a more specific question than “does this feature work” — namely, “does this number mean what the copy says it means.”
The fix, mechanically
For the missing upgrade path: replace the dead-end notice with a modal that fires right when the limit is hit, states what’s locked, and links into the settings tab where the license key goes. No new account flow, no external page. The whole path from “I hit a wall” to “I can do something about it” stays inside the plugin.
For the counter that lied about being lifetime: rewrite the copy to match what the counter does, or rewrite the counter to match the intent — pick one, but stop letting them disagree.
For the race condition: reserve-then-confirm. Reserve a use before the action runs, confirm it after the action completes, roll back the reservation if the action fails. That keeps the number on screen and the number actually enforced from drifting apart.
Where this actually stands
I want to be straight about what this post is. It’s not a results post. I shipped this fix recently and don’t have enough days of data to say whether a visible upgrade path moves conversion at all. The real numbers right now, across all 7 plugins: 328 total downloads, 295 Gumroad visits, $34.98 from 2 sales in the last 30 days. Conversion is close to zero. This fix doesn’t claim to have changed that — it closes a gap that was obviously there, and whatever happens to the numbers from here won’t be because of a missing button anymore.
If you’re maintaining a similar licensing setup, the actual move is to go look at your own free-tier limit moment right now — not the code, the screen, as it would look to someone who’s never opened your settings tab. I wrote more about the early sales pattern behind which plugins convert at all in my first two sales post, and the inbound-vs-self-archive split that seems to predict it in six plugins, two sold. I ran into a similar silent-failure pattern in n8n — a node quietly overwriting the data flowing through it — while building a Gmail support triage workflow, in a related n8n debugging post.
You can see everything I’ve shipped so far at ibrh96.gumroad.com.