Push a planned workout from the Hub to TP (ZWO) or a Garmin/Wahoo head unit (FIT).
The Hub can emit a planned workout as either:
.zwo — TrainingPeaks's Zwift-derived XML format. Drop into
TP → Workout Library → Import. Cycling only (TP's own constraint)..fit — the binary format Garmin and Wahoo head units consume.
Sideload to your watch / bike computer or push via Garmin Connect.Both downloads live on the workout detail page, under Export workout.
After the 2026-05 import bisect:
We previously emitted a <category>Cycling</category> element that
TP rejected with "We currently only support power based cycling
workouts in ZWO format." It turns out <category> is not part of
the Zwift ZWO schema — Zwift itself never emits it. Removing it
was the cure. The exporter now emits files in the canonical Zwift
order: author → name → description → sportType → tags → workout.
If you ever see that rejection again, it means a regression has
crept back into core/services/zwo_export.py. The regression test
test_no_category_element guards against that.
.zwo — TP only
accepts cycling there. FIT export works for run.We keep a bisect probe suite in docs/zwo_test_suite/ (14+ files)
that tests specific ZWO features against TP's import path. If you
want to confirm that, say, a new <FreeRide> block or a MaxEffort
marker imports correctly, generate the probes (python manage.py
emit_zwo_test_suite) and drop the files into TP one by one. The
README in that folder explains the workflow.
Still stuck? Ask us a question and we'll write up an answer.
Ask a question