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.