Sync Hevy gym workouts to Garmin Connect — exercise mapping, FIT file generation, and automatic upload
Project description
hevy2garmin
Sync your Hevy gym workouts to Garmin Connect with correct exercise names, sets, reps, weights, calorie estimation, and optional heart rate overlay from your Garmin watch.
Hevy Pro required. The Hevy API is only available with a Hevy Pro subscription. Without it, hevy2garmin cannot access your workouts.
Why?
Hevy is great for tracking gym workouts but doesn't sync to Garmin. This tool bridges the gap:
- Maps 433+ Hevy exercises to Garmin FIT SDK categories so bench press shows as bench press, not "Other"
- Generates proper FIT files with exercise structure, sets, reps, weights, and timing
- Uploads to Garmin Connect with the correct activity name and a detailed description
- Estimates calories using the Keytel formula (weight, age, VO2max, heart rate)
- Overlays heart rate data from your Garmin watch onto workout charts with per-exercise segments
- Tracks synced workouts so nothing gets duplicated
Screenshots
| Workouts | Mappings |
|---|---|
| HR Timeline | Calorie Breakdown |
Requirements
- Hevy Pro subscription (required for API access)
- A Garmin Connect account
- Python 3.10+ (for local install only, not needed for the Vercel deploy)
Quick Start
Pick the option that fits you best:
Vercel Deploy (no coding required)
Deploy from your phone or computer in about 5 minutes. No terminal or coding needed.
You need Hevy Pro for API access. Free Hevy accounts cannot use hevy2garmin.
Step 1: Get your Hevy API key
Open hevy.com/settings, scroll to Integrations & API, click Generate API Key, and copy it. If you don't see this section, you need to upgrade to Hevy Pro.
Step 2: Create a free GitHub account (skip if you already have one)
Sign up at github.com. You'll use this to sign into Vercel too.
Step 3: Create a GitHub access token
This token lets hevy2garmin set up automatic syncing on your behalf. Open this link (sign in if prompted):
- Set Expiration to No expiration (otherwise auto-sync stops when it expires)
- Scroll to the bottom, click Generate token
- Copy the token immediately (starts with
ghp_). GitHub only shows it once.
Step 4: Fork the repo
Fork hevy2garmin on GitHub -- click the green Create fork button. This gives you your own copy that stays linked to the original, so you can pull updates later with one click.
Step 5: Deploy to Vercel
- Go to vercel.com/new and sign in with GitHub
- Find hevy2garmin in your repo list and click Import
- If you see an Integrations section, click Add next to Neon (this is the free database that stores your sync history). If you don't see it, no problem -- after deploy, go to your project's Storage tab and add Neon Postgres from there.
- Environment Variables -- fill in these 4 values:
| Field | What to paste |
|---|---|
HEVY_API_KEY |
The API key from step 1 |
GARMIN_EMAIL |
Your Garmin Connect email |
GARMIN_PASSWORD |
Your Garmin Connect password |
GITHUB_PAT |
The token from step 3 |
- Click Deploy and wait about a minute for it to build.
Step 6: Connect Garmin
Click Continue to Dashboard, then Visit to open your app. Bookmark this URL -- it's your dashboard.
On the setup page, enter your Garmin email and password and click Connect.
- If your Garmin account does not have 2FA enabled, you're connected in a second. That's it.
- If your Garmin account has 2FA enabled, Garmin emails you a 6-digit code. A code input appears on the page, paste the code, click Verify. Done.
Then click Save & Continue.
Garmin blocks automated logins from cloud servers (AWS, Azure, Vercel), so hevy2garmin routes the login through a Cloudflare Worker that runs on Cloudflare's edge network. Garmin accepts those IPs, so the whole flow happens in a single click from the dashboard -- no browser tab switching, no URL copying.
Fallback for edge cases: on the rare occasion Garmin doesn't accept the direct login (most often when the account has an unusual security configuration), the setup page automatically reveals the old "Sign into Garmin in a new tab and paste the URL back" flow as a safety net. You don't need to do anything differently -- just follow the instructions the page shows you.
Step 7: Sync your workouts
You're on the dashboard. Click Sync All Workouts to backfill your history. The app syncs one workout at a time (you can close the page and come back, it picks up where it left off).
EU users: If you see an upload consent error, go to Garmin Connect Settings > scroll to Data > enable Device Upload. This is a one-time Garmin GDPR requirement.
To keep future workouts syncing automatically, toggle Auto-sync on the dashboard. This creates a background job that syncs new workouts every 2 hours.
That's it. Check Garmin Connect to see your workouts with proper exercise names, sets, reps, and weights.
Web Dashboard (local install)
pip install hevy2garmin
hevy2garmin serve
Not on PyPI yet? Install from source:
git clone https://github.com/drkostas/hevy2garmin.git && cd hevy2garmin && pip install .
Open localhost:8123. The setup wizard walks you through connecting Hevy and Garmin.
Once you click Sync Now, your workouts appear in Garmin Connect within a few seconds. Enable auto-sync on the dashboard to keep things synced on a schedule (30 min to 24 hours).
To keep the server running in the background:
nohup hevy2garmin serve > /dev/null 2>&1 &
systemd service file (Linux)
Save as /etc/systemd/system/hevy2garmin.service:
[Unit]
Description=hevy2garmin dashboard
After=network.target
[Service]
ExecStart=hevy2garmin serve
Restart=always
User=your-username
Environment=HEVY_API_KEY=your-key
Environment=GARMIN_EMAIL=your-email
[Install]
WantedBy=multi-user.target
Then sudo systemctl enable --now hevy2garmin.
CLI
pip install hevy2garmin
# Interactive setup (Hevy API key + Garmin credentials)
hevy2garmin init
# Sync your 10 most recent workouts
hevy2garmin sync
# List recent workouts (checkmark = already synced)
hevy2garmin list
# Check sync status
hevy2garmin status
# Dry run (generate FIT files without uploading)
hevy2garmin sync --dry-run
# Sync last 5 workouts only
hevy2garmin sync -n 5
After syncing, check Garmin Connect to see your workouts.
Recurring sync without the dashboard: set up a crontab after running hevy2garmin init:
# Sync every 2 hours (uses credentials saved by hevy2garmin init)
0 */2 * * * hevy2garmin sync
Docker
git clone https://github.com/drkostas/hevy2garmin.git
cd hevy2garmin
docker build -t hevy2garmin .
Before running in Docker, you need Garmin auth tokens. Either:
- Run
pip install hevy2garmin && hevy2garmin initlocally (if you have Python), or - Run
docker run -it -v ~/.garminconnect:/root/.garminconnect hevy2garmin initto set up inside Docker interactively
Web dashboard with auto-sync:
docker run -d -p 8123:8123 --restart unless-stopped \
-v ~/.hevy2garmin:/root/.hevy2garmin \
-v ~/.garminconnect:/root/.garminconnect \
-e HEVY_API_KEY=... \
-e GARMIN_EMAIL=... \
hevy2garmin serve
Open localhost:8123 and enable auto-sync on the dashboard.
One-off sync:
docker run --rm \
-v ~/.hevy2garmin:/root/.hevy2garmin \
-v ~/.garminconnect:/root/.garminconnect \
-e HEVY_API_KEY=... \
-e GARMIN_EMAIL=... \
hevy2garmin sync
Python API
pip install hevy2garmin
Before using the API, make sure credentials are available via ~/.hevy2garmin/config.json (run hevy2garmin init), environment variables, or pass them directly.
from hevy2garmin.sync import sync
# Uses config from ~/.hevy2garmin/config.json (or env vars)
result = sync()
print(f"Synced: {result['synced']}, Skipped: {result['skipped']}")
# Or pass credentials directly (no config file needed)
result = sync(hevy_api_key="...", garmin_email="...", garmin_password="...")
# Just the exercise mapper
from hevy2garmin.mapper import lookup_exercise
cat, subcat, name = lookup_exercise("Bench Press (Barbell)")
# (0, 1, "Bench Press (Barbell)")
# Just FIT generation (see Hevy API docs for workout dict format:
# https://docs.hevy.com/#tag/workout/operation/workout)
from hevy2garmin.fit import generate_fit
result = generate_fit(hevy_workout_dict, hr_samples=None, output_path="workout.fit")
For cloud deployments (Vercel, CI/CD), install with Postgres support:
pip install hevy2garmin[cloud]
This adds psycopg2-binary and enables automatic Postgres backend detection via DATABASE_URL.
Getting Your Hevy API Key
Hevy Pro is required. API access is not available on the free plan.
- Go to Hevy Settings > Integrations & API
- Click Generate API Key and copy it
- Paste it into
hevy2garmin init, the web dashboard setup, or set asHEVY_API_KEYenv var
If you don't see the Integrations & API section, you need to upgrade to Hevy Pro.
Credentials
Three ways to provide credentials (in order of precedence):
- CLI flags:
--hevy-api-key,--garmin-email,--garmin-password - Environment variables:
HEVY_API_KEY,GARMIN_EMAIL,GARMIN_PASSWORD - Config file:
~/.hevy2garmin/config.json(created byhevy2garmin initor the web dashboard)
See .env.example for all available env vars.
Garmin authentication: Only needs the password for initial login. After that, tokens are cached (in ~/.garminconnect locally or in Postgres for cloud deploys) and refresh automatically.
Cloud deploys (Vercel): Garmin blocks automated logins from cloud servers, so hevy2garmin routes the login through a Cloudflare Worker (
hevy2garmin-exchange-di.gkos.workers.dev) that runs on Cloudflare's edge network. The Worker accepts your email + password from the setup page, completes the login (including 2FA if enabled), and returns a DI OAuth token that hevy2garmin stores in your Postgres database. This happens in a single click from the setup wizard. On the rare occasion Garmin rejects the direct login, the setup page automatically falls back to a "sign in via browser, paste the URL back" flow.
Updating
Vercel (fork-based deploy)
Your Vercel project is linked to your GitHub fork. To get the latest version:
- Go to your fork on GitHub (e.g.
github.com/your-username/hevy2garmin) - Click Sync fork → Update branch (this pulls the latest changes from the original repo)
- Vercel auto-deploys when your fork updates. Wait ~1 minute for the build to finish.
- Open your dashboard URL and reconnect Garmin if prompted (token format may change between versions)
If you deployed before April 2026 using the old one-click button, your repo may be a standalone copy instead of a fork ("Sync fork" button won't appear). To migrate:
- Fork hevy2garmin to your GitHub account
- In Vercel dashboard → your project → Settings → Git → disconnect the old repo
- Connect the new fork → redeploy
- Your Neon database and env vars stay intact (they're on the Vercel project, not the repo)
- You can delete the old standalone copy from GitHub to avoid having two "hevy2garmin" repos
pip
pip install --upgrade hevy2garmin
Docker
cd hevy2garmin
git pull origin main
docker build -t hevy2garmin .
Git clone (local)
cd hevy2garmin
git pull origin main
pip install -e .
Activity Description
When hevy2garmin syncs a workout, it adds a text description to the Garmin activity summarizing your session:
🏋️ Push Day
⏱️ 52 min
🔥 387 kcal
❤️ avg 118 bpm
• Bench Press (Barbell): 3 sets · 80.0kg × 8
• Incline Dumbbell Press: 3 sets · 28.0kg × 10
• Cable Fly: 3 sets · 15.0kg × 12
— synced by hevy2garmin
This is visible in the activity details on Garmin Connect and any connected apps (Strava, etc.). Cardio exercises show distance and duration instead of weight and reps.
Enhance Watch Activities (opt-in)
By default, hevy2garmin creates a new Garmin activity from your Hevy workout using your watch's continuous HR monitoring (~2 min sampling). This works without any behavior change.
If you start a Strength Training activity on your Garmin watch when you hit the gym, you can enable Enhance Watch Activities in the config ("merge_mode": true). hevy2garmin will detect the matching watch activity and push your Hevy exercise data directly into it instead of creating a new activity. Benefits:
- 1-second HR sampling (vs ~2 min in continuous monitoring)
- Training effect, EPOC, recovery time, and VO2max impact all count (Garmin ignores these for manually uploaded activities)
- Correct Strava timestamps (watch-synced activities use the real time, not upload time)
- Single activity on Garmin (no duplicate)
If no matching watch activity is found, hevy2garmin falls back to the default flow automatically. Matching requires 70% temporal overlap with a Strength Training activity within 20 minutes of the Hevy workout start time.
How It Works
- Pulls workouts from the Hevy API
- Maps each exercise to a Garmin FIT SDK category and subcategory (433+ built-in mappings, plus any custom ones you add)
- Generates a structured FIT file with timing, sets, reps, weights, and calories
- Optionally fetches HR data from Garmin daily monitoring and overlays it on the workout
- Authenticates with Garmin via garmin-auth (self-healing OAuth)
- Uploads the FIT file, renames the activity, and sets the description
- Tracks synced workouts in SQLite (local) or Postgres (cloud) to avoid duplicates
Exercise Mapping
433+ Hevy exercises are mapped to Garmin FIT SDK categories. If an exercise isn't mapped it falls back to "Unknown" (category 65534). The web dashboard shows unmapped exercises and lets you add custom mappings with a few clicks. You can also add them via CLI:
hevy2garmin map "My Custom Exercise" --category 28 --subcategory 0
Development
git clone https://github.com/drkostas/hevy2garmin.git
cd hevy2garmin
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v
To test the Postgres backend locally:
pip install -e ".[dev,cloud]"
DATABASE_URL=postgresql://user:pass@localhost:5432/hevy2garmin pytest tests/ -v
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hevy2garmin-0.4.0.tar.gz.
File metadata
- Download URL: hevy2garmin-0.4.0.tar.gz
- Upload date:
- Size: 484.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f08a5e3a850055bef43fe44df02b3e67eaaf5e9ba46e710d5339651674ccecb
|
|
| MD5 |
e1455ea3c67ffe0cb3160ce23be13e60
|
|
| BLAKE2b-256 |
0a8b2d34b1b8733bedb2fea806a8355c792278eec2c1a021e572d9d2f85bbfa4
|
Provenance
The following attestation bundles were made for hevy2garmin-0.4.0.tar.gz:
Publisher:
publish.yml on drkostas/hevy2garmin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hevy2garmin-0.4.0.tar.gz -
Subject digest:
9f08a5e3a850055bef43fe44df02b3e67eaaf5e9ba46e710d5339651674ccecb - Sigstore transparency entry: 1341575207
- Sigstore integration time:
-
Permalink:
drkostas/hevy2garmin@f967fbed63fba821eb4083981f097ab931cc3d98 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/drkostas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f967fbed63fba821eb4083981f097ab931cc3d98 -
Trigger Event:
push
-
Statement type:
File details
Details for the file hevy2garmin-0.4.0-py3-none-any.whl.
File metadata
- Download URL: hevy2garmin-0.4.0-py3-none-any.whl
- Upload date:
- Size: 106.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d77854df89395eebe3729b6a36638ab5cfb8823b99d9d6e635a7aeeb844aa99
|
|
| MD5 |
9cb4d6db31152da78e0d17f49403c121
|
|
| BLAKE2b-256 |
ee7df90aeb27599236fb098a21b333260e17e47d407527db408409792c7ef7af
|
Provenance
The following attestation bundles were made for hevy2garmin-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on drkostas/hevy2garmin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hevy2garmin-0.4.0-py3-none-any.whl -
Subject digest:
0d77854df89395eebe3729b6a36638ab5cfb8823b99d9d6e635a7aeeb844aa99 - Sigstore transparency entry: 1341575275
- Sigstore integration time:
-
Permalink:
drkostas/hevy2garmin@f967fbed63fba821eb4083981f097ab931cc3d98 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/drkostas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f967fbed63fba821eb4083981f097ab931cc3d98 -
Trigger Event:
push
-
Statement type: