Skip to content

Developing an Obsidian Plugin for Garmin Connect

An open-source Obsidian plugin that syncs Garmin Connect metrics to your vault and visualizes them on desktop and mobile.

For those of you who don't know, Obsidian is a note-taking app built on plain files. One of the neat quirks of the app, and part of its core functionality, is that you can link notes to each other and visualize your digital brain growing, so to speak — a collection of everything you've recorded, from study notes and thoughts to daily entries.

But plugins are where the real magic happens. Any non-native functionality can be developed and added to your vault, so the possibilities become pretty much limitless. Needless to say, if you haven't tried the app, I highly recommend you do! It might be a little daunting at first, but stick with it and it becomes really fun and rewarding.

Back to the main topic at hand. Garmin Connect is used to connect to Garmin wearables and store the data they measure: sleep, heart rate, steps, workouts, and a lot more. It's great in the sense that pretty much all the health data you could ever need is stored there. You can view it through Garmin Connect on mobile and the web, and Garmin provides a few ways to export it, but there isn't a nice personal-use that lets you freely query all of it.

Wouldn't it be great if you could store, visualize, and query that data in Obsidian...

Well, fear not, we built a plugin that brings the two together. Garmin authentication is seamlessly integrated into your vault, each day's metrics are written into your daily notes as . Those properties can be organized in a queryable table, and the data can be visualized through an interactive dashboard directly in your vault.

Last but not least, it's compatible with both mobile and desktop.

The rest of this post describes the major pitfalls we had to overcome and the end result.

Garmin API

Garmin does have an official Connect Developer Program, but it's explicitly intended for business use. For an individual who just wants to pull their own step count into a side project, there isn't a self-serve , client, or documented consumer .

Fortunately, there was an actively maintained repo, cyberjunky/python-garminconnect, which made the process so much easier.

It gave us two very important things:

  1. 01
    It validated that was possible.
  2. 02
    It mapped out Garmin's non-public endpoints and HTTP / structures.

All we needed to do was translate the relevant logic into TypeScript while avoiding Node.js or Electron APIs that would make the plugin incompatible with mobile.

For all my non-technical folks reading this, please do not fret over the seemingly gibberish words. Truth be told, we software engineers don't like words or reading either. After all, it's 2026 — if it's not short-form content with Subway Surfers taking up the bottom half, is it even real content?

Unfortunately, I'm all out of Subway Surfers footage, but here's a diagram I made instead. This format of illustration is called a — SD if ya nasty.

A sequence diagram: the plugin asks Garmin's sign-in service for tokens, keeps them, then calls the Connect endpoints one day at a time and writes each day into a note.

We had a bit of a scare right off the bat. Garmin's sign-in flow has bot-protection measures in front of it, and python-garminconnect has gone pretty far to work around false positives. Some of its authentication strategies impersonate browser or mobile , and some of its web-login paths deliberately pause before submitting a login.

Inside Obsidian, you get requestUrl, and you don't get the same low-level control over the network stack.

So before writing anything else, we tested whether authentication would work without trying to imitate one of those browser TLS fingerprints. We tested this on desktop and iOS, and authentication was successful on both.

That doesn't mean Garmin ignores TLS fingerprinting everywhere. What it does mean is that, in our testing, the mobile sign-in path worked through Obsidian without needing that extra impersonation layer.

Great news for us.

Garmin could tighten this restriction eventually, but for now, we're good.

Mobile Compatibility

Obsidian on mobile runs in a -based environment. This is a bit of an issue since and APIs available for desktop plugins, are incompatible for mobile.

Garmin Health Sync is another Obsidian Garmin plugin and something I drew a lot of inspiration from. Initially, I forked the repo and started building on top of it, ready to send PRs with the dashboard view and other features I had built.

Eventually, though, my version started to diverge too much. I wanted headless authentication, mobile compatibility, and a fairly opinionated dashboard, so it made more sense to build my own plugin from the ground up.

One limitation of Garmin Health Sync for my use case is that authentication uses an Electron browser window, which makes that flow desktop-only.

As discussed earlier, we found another route.

For the network layer, the main piece was using Obsidian's requestUrl. It's similar to the standard fetch() API, but Obsidian provides it without normal browser restrictions, and it works across Obsidian's supported platforms.

Visualizing the Numbers

So now we can authenticate, and pulling data is rather simple.

Unfortunately, a wall of numbers in a file doesn't help much.

Okay, let's take a step back. Here's what's going on.

First, metrics land as note , so Dataview and Bases can query them. Each sync walks backwards day by day and only updates a note when something has actually changed. We also have to be careful about how aggressively we hit Garmin's endpoints so we don't get .

Second, a dashboard.

Interactive Dashboard

Data through 12 Sep · 380 days stored
Steps
5,754
↑+3.3%vs prior week
Sleep
7h 2m
↑+0.2%vs prior week
Resting HR
48 bpm
↓-2.5%vs prior week
Body Battery
91
↓-3.9%vs prior week
HRV
45 ms
↑+5.1%vs prior week
Readiness
66
↑+8.2%vs prior week
VO2 Max
48
↓-0.8%vs prior week
Endurance
7,142
↓-1.6%vs prior week
5K prediction
24:38
↑+0.8%vs prior week
Steps
Daily total · goal 10,000 (dashed)
Sleep
Hours by stage
DeepLightREMAwake
Resting heart rate
bpm
HRV
Overnight average, ms
Body Battery
Daily low to high
Training readiness
Score out of 100
VO2 Max
ml/kg/min
Endurance score
Garmin endurance score
5K prediction
Predicted finish time
10K prediction
Predicted finish time
Half marathon prediction
Predicted finish time
Marathon prediction
Predicted finish time

The uses . We had the choice between that, native , or , and I ended up going with Svelte because its compiled approach fit what I wanted from a lightweight Obsidian plugin.

React goes through a render and reconciliation step before committing the necessary changes to the DOM, often described in terms of a . Svelte takes a different approach: the compiler can generate targeted updates to the actual .

That doesn't magically make Svelte faster in every situation, but for a small plugin where bundle size and UI overhead matter, I liked the trade-off.

The charts are made using 's. I also chose not to pull in a full Svelte component library because I wanted to keep the plugin bundle small. Instead, I can use existing libraries as design references and hand-roll the components I actually need.

What's next

The plugin is not perfect, and never will be. The life of a developer and active maintainer is much like and his endless task of rolling the boulder up the mountain.

But every day we go back to rolling that boulder and try to take another step forward.

Here are some things we plan to add:

  • Handle accounts
  • Improve dashboard UI
  • Add pipelines to autodetect if anything breaks or Garmin changes its API
  • Create custom components that you can embed directly in your Markdown files

Acknowledgements

This project wouldn't have been possible without the developer community. I want to properly acknowledge the openly available projects and resources that helped me get here:

  • Garmin Health Sync, for the idea and for showing how far file properties and daily notes can go. I wanted headless sign-in, cross-platform compatibility, and a dashboard, but the original inspiration started there.
  • cyberjunky/python-garminconnect, an actively maintained Garmin Connect library that saved us a huge amount of reverse-engineering work.
  • health-md, which I plan to use as an inspiration around health-data UI and visualization components.
  • The Obsidian community, for the support and the beta testing.