Upload an activity (.gpx or .fit) to Strava and update values
pip install -r requirements.txt
Or directly:
pip install requests requests-toolbelt
On Debian/Ubuntu where system Python is externally managed (PEP 668), use a virtual environment instead:
python3 -m venv .venv
.venv/bin/pip install requests requests-toolbelt
.venv/bin/python ./StravaUploader.py test.gpx
Upload an activity using a saved session:
./StravaUploader.py activity.gpx
The uploader sends the file as-is to Strava, which accepts common formats such as
.gpx and .fit; the examples below use .gpx.
| Option | Description |
|---|---|
gpx (positional) |
Path to the activity file (.gpx/.fit) to upload. |
--session PATH |
Path to the saved session-cookies JSON (default: session.json). |
--name TEXT |
Rename the uploaded activity. |
--type TYPE |
Set the activity type (e.g. GravelRide, VirtualRide, Run). Run --help for the full list. |
--photos P1 [P2 ...] |
One or more image files to attach to the activity. |
--tags TEXT |
Comma/space separated tags. |
--private {everyone,followers,only_me} |
Activity visibility. |
--mute |
Mute the activity from home and club feeds. |
StravaUploader.py uploads to Strava using a saved session cookie. Login is only supported via a
session file; automated Google email/password login is not available.
Sessions expire after some time. If uploads start failing with "No valid session found",
re-export a fresh _strava4_session into session.json (see below).
Export a logged-in session once and reuse it:
- Log in to https://www.strava.com in a browser (via Google or otherwise).
- Open DevTools (F12) -> Console and run
document.cookie. - Copy the value of
_strava4_session=...intosession.jsonnext to the script:
{
"cookies": [
{
"name": "_strava4_session",
"value": "PASTE_VALUE_HERE",
"domain": ".strava.com",
"path": "/"
}
]
}- Run
./StravaUploader.py out.gpx(no credentials needed while the session is valid).
StravaUploader.py names the activity after the uploaded file by default. To override it, pass
--name; after the file finishes processing the activity is renamed on Strava:
./StravaUploader.py out.gpx --name "Morning Ride"
Pass --type to change the activity type after upload (e.g. GravelRide, VirtualRide, Run).
Run ./StravaUploader.py --help to list all valid types:
./StravaUploader.py out.gpx --type GravelRide --name "Gravel Ride"
Pass one or more image files with --photos to attach them to the uploaded activity. Images are
uploaded through Strava's photo endpoint and then attached by submitting the activity edit form:
./StravaUploader.py out.gpx --photos screenshot1.jpg screenshot2.jpg
The name, type and photos are all applied in a single edit-form submission, so when
--name/--type are combined with --photos they are saved together in one transaction.
Photo attachment is best-effort: if an individual image fails to upload or the final save fails, the already-uploaded activity is left untouched and the script just prints what went wrong.
These are also applied via the activity edit form (the same /activities/<id>/edit submission used
for name/type/photos), so they can be combined with any of the other options in one transaction.
--tags— comma or space separated tags, e.g.--tags "ride, commute"or--tags ride commute.--private— activity visibility:everyone,followers, oronly_me.--mute— mute the activity from home and club feeds.
./StravaUploader.py out.gpx --tags "ride, commute" --private followers --mute
These map to Strava's edit-form fields: tags → activity[tag_list], privacy → activity[visibility]
(everyone / followers_only / only_me), and mute → activity[hide_from_home]. If the mute
checkbox is not present in the form (e.g. Strava changes its UI), the script prints a warning and
skips that part of the update rather than failing.
StravaPhotoDownloader.py downloads the photos attached to an existing activity. It uses the same
saved session as the uploader, loads the activity page, reads the embedded photosJson data and
downloads each photo's enhanced image when present, otherwise its large version:
./StravaPhotoDownloader.py 20629200268
Photos are saved into photos/ by default, skipping files that already exist. Videos are skipped
unless --videos is passed, in which case the video file is downloaded instead of its poster image.
./StravaPhotoDownloader.py 20629200268 --videos
| Option | Description |
|---|---|
activity_id |
The activity id (or full activity URL) to download photos from. |
--session PATH |
Path to the saved session-cookies JSON (default: session.json). |
--outdir PATH |
Directory to download photos into (default: photos). |
--videos |
Also download videos attached to the activity (skipped by default). |