Python script to fetch Garmin Connect stats and update Obsidian note frontmatter.
Key Features:
- Automatically detects dates from
YYYY-MM-DD.mdfilename pattern - Updates Obsidian note frontmatter with Garmin daily stats
- Configurable field mappings for custom frontmatter properties
- Activity-based tracking by sport type (cycling, running, swimming, etc.)
- Works seamlessly with Obsidian's Daily Notes plugin
- Install dependencies with uv:
uv sync- Set up your Garmin Connect credentials by creating a
.envfile:
cp .env.example .envThen edit .env with your credentials:
GARMIN_EMAIL=your-email@example.com
GARMIN_PASSWORD=your-password
Important:
- Credentials MUST be set in
.envfile (required for automated script execution) - MFA (Multi-Factor Authentication) is currently NOT supported
- If you have MFA enabled on your Garmin account, you'll need to disable it to use this script
- First run authentication:
On first run, the script will authenticate with Garmin Connect and save tokens to ~/.garmin_tokens. Future runs will use these cached tokens automatically.
-
Install the Shell Commands plugin in Obsidian
-
Open Settings → Shell commands → New shell command
-
Add this command (choose one option):
Option A - Use full path to uv (Recommended):
cd /path/to/obsidian-garmin-connect-scripts && ~/.local/bin/uv run main.py "{{file_path:absolute}}"
Option B - Source your shell profile:
source ~/.zshrc && cd /path/to/obsidian-garmin-connect-scripts && uv run main.py "{{file_path:absolute}}"
Note: Replace
/path/to/obsidian-garmin-connect-scriptswith your actual project directory. -
Configure the command:
- Alias: Give it a name like "Sync Garmin Stats"
- Hotkey (optional): Assign a keyboard shortcut in Settings → Hotkeys
- Icon (optional): Add to ribbon or command palette
-
Now you can:
- Use the command palette (Cmd/Ctrl+P) and search "Sync Garmin Stats"
- Use your custom hotkey
- Click the ribbon icon if configured
If you want different mappings per vault or note type, modify the command:
cd /path/to/obsidian-garmin-connect-scripts && ~/.local/bin/uv run main.py "{{file_path:absolute}}" --map steps:totalSteps --map distance:totalDistanceMeters- The script will update the currently open note when triggered from Obsidian
- If your daily notes use
YYYY-MM-DD.mdnaming (Obsidian's default), the date will be automatically detected from the filename - Make sure your
.envfile is configured with Garmin credentials - The first run will authenticate and cache tokens for future use
- You'll see a success message in the shell output pane
- Works perfectly with Obsidian's Daily Notes plugin (uses
YYYY-MM-DD.mdformat by default)
"uv: command not found"
- Use the full path:
~/.local/bin/uvinstead of justuv - Or find your uv location: run
which uvin terminal and use that path
"Permission denied"
- Make sure the script has execute permissions:
chmod +x main.py
"Missing GARMIN_EMAIL or GARMIN_PASSWORD"
- Ensure your
.envfile is in the project root directory with both variables set - Check that the shell command is running from the correct directory (the
cdpart)
"403 Forbidden" or authentication errors
- Your Garmin session tokens may be expired or invalid
- Delete the token cache:
rm -rf ~/.garmin_tokens - Run the script again to re-authenticate
- Check your Garmin credentials in
.envare correct - If you have MFA enabled, disable it - MFA is not supported for automated scripts
"Too many requests" or rate limiting
- Garmin has rate limits on API calls
- Wait a few minutes before trying again
Run the script from command line with the path to your Obsidian note:
uv run main.py /path/to/your/note.mdThe script will:
- Authenticate with Garmin Connect (tokens cached in
~/.garmin_tokens) - Automatically detect the date from the filename (if it matches
YYYY-MM-DD.mdpattern) - Fetch activity stats for the detected date (or today's date as fallback)
- Update the note's frontmatter with default fields
The script automatically extracts dates from filenames that follow the YYYY-MM-DD.md pattern:
# Automatically uses 2026-01-13 from filename
uv run main.py 2026-01-13.md
# Falls back to today's date (filename doesn't match pattern)
uv run main.py my-daily-note.mdDate priority:
--dateargument (if provided) - highest priority- Date extracted from filename (
YYYY-MM-DD.mdpattern) - Today's date (fallback)
This makes it easy to update historical daily notes without manually specifying dates.
By default, the following fields are updated:
steps: Total stepsdistance_km: Total distance in kilometerscalories: Total kilocalories burnedactive_minutes: Moderate + vigorous intensity minutes
Map custom frontmatter properties to Garmin stats:
uv run main.py note.md --map steps:totalSteps --map calories:totalKilocalories --map floors:floorsAscendedAdd more fields like heart rate, floors, calories, etc.:
uv run main.py note.md --map steps:totalSteps --map distance_km:totalDistanceMeters --map active_minutes:moderateIntensityMinutes --map floors:floorsAscended --map calories:totalKilocalories --map resting_hr:restingHeartRateTrack time spent on different sports by using the activity: prefix:
uv run main.py note.md --map cycling_minutes:activity:cycling --map running_minutes:activity:running --map swimming_minutes:activity:swimming --map other_minutes:activity:otherSupported activity categories:
activity:cycling- Cycling, mountain biking, road biking, gravel, indoor cyclingactivity:running- Running, treadmill, trail running, track runningactivity:swimming- Swimming, lap swimming, open wateractivity:walking- Walking, hikingactivity:other- Any activity not in the above categories
The script automatically:
- Fetches your activities for the day
- Categorizes them by sport type
- Sums up the duration (in minutes) for each category
- Updates your note with the totals
See all available Garmin Connect stats:
uv run main.py note.md --show-availableThis will display all stats from Garmin Connect with their values, helping you decide which fields to map.
You can explicitly specify a date to override the automatic detection:
# Override automatic date detection
uv run main.py 2026-01-13.md --date 2026-01-04
# Specify date when filename doesn't have a date pattern
uv run main.py my-note.md --date 2026-01-04This is useful when:
- You want to fetch stats for a different date than the filename suggests
- The filename doesn't follow the
YYYY-MM-DD.mdpattern but you don't want today's date
--map KEY:VALUEor-m KEY:VALUE: Map frontmatter property to Garmin stat (can be used multiple times)--show-availableor-s: Display all available Garmin stats and their values--date DATEor-d DATE: Override date detection and fetch stats for specific date (ISO format: YYYY-MM-DD)--helpor-h: Show help message
Given a note named 2026-01-05.md with frontmatter:
---
title: Daily Log
date: 2026-01-05
---
# My Daily LogRun:
# Date automatically extracted from filename (2026-01-05)
uv run main.py 2026-01-05.mdResult:
---
title: Daily Log
date: 2026-01-05
steps: 8542
distance_km: 6.32
calories: 2341
active_minutes: 45
---
# My Daily LogIf you want different property names or additional fields:
# Date automatically extracted from filename
uv run main.py 2026-01-05.md --map daily_steps:totalSteps --map km_walked:totalDistanceMeters --map floors:floorsAscended --map calories:totalKilocaloriesThis will create:
---
title: Daily Log
date: 2026-01-05
daily_steps: 8542
km_walked: 6.32
floors: 12
calories: 2341
---Track your daily activities by sport:
# Date automatically extracted from filename
uv run main.py 2026-01-05.md --map steps:totalSteps --map cycling_minutes:activity:cycling --map running_minutes:activity:running --map walking_minutes:activity:walking --map other_minutes:activity:otherIf you did a 45-minute bike ride and a 30-minute run on 2026-01-05:
---
title: Daily Log
date: 2026-01-05
steps: 8542
cycling_minutes: 45
running_minutes: 30
walking_minutes: 0
other_minutes: 0
---Run with --show-available to see what Garmin data you can use:
# Works with any filename
uv run main.py 2026-01-05.md --show-availableOutput:
Available Garmin Connect stats:
------------------------------------------------------------
floorsAscended: 12
moderateIntensityMinutes: 30
totalDistanceMeters: 6324
totalKilocalories: 2341
totalSteps: 8542
vigorousIntensityMinutes: 15
...
------------------------------------------------------------