Skip to content

Convert ZWO to Garmin ​

Scope

Converts structured workouts (steps and targets), not recorded activities. See Workouts vs. activities.

Take a Zwift ZWO workout into Garmin Connect so you can run a session you built in Zwift on your Garmin watch or head unit. Kaiord converts ZWO to the Garmin Connect workout format (GCN JSON) — free and in-browser in the Kaiord Editor (no account, no upload), or via the CLI and TypeScript SDK. Conversions go through Kaiord's canonical KRD format and stay within round-trip tolerances (time ±1 s).

Three ways to convert ​

1. Editor (in the browser) ​

Open kaiord.com/app, choose your .zwo file (GCN is already selected as the export format), and download the result. To send it straight to your watch, use the Editor's Garmin sync (backed by the garmin-bridge extension).

2. CLI ​

Zwift power is % FTP, so pass your FTP in watts with --ftp:

bash
pnpm add -g @kaiord/cli
kaiord convert -i workout.zwo -o workout.gcn --ftp 250

To push directly to Garmin Connect (after kaiord garmin login):

bash
kaiord garmin push -i workout.zwo --input-format zwo --ftp 250

3. SDK ​

ts
import { fromText, toText } from "@kaiord/core";
import { zwiftReader } from "@kaiord/zwo";
import { createGarminWriter } from "@kaiord/garmin";
import { readFile, writeFile } from "node:fs/promises";

const zwo = await readFile("workout.zwo", "utf-8");
const krd = await fromText(zwo, zwiftReader);
const gcn = await toText(krd, createGarminWriter({ ftpWatts: 250 }));
await writeFile("workout.gcn", gcn);

What survives the conversion ​

DataZWO (Zwift)Garmin Connect (GCN)Result
Step order & namesblocksworkout stepsPreserved
Time durationsDuration secondstime durationsPreserved (±1 s)
Power targets% FTP (Power fraction)wattsConverted using your FTP (see below)
RampsWarmup / Cooldown / Rampranged targetConverted using your FTP (see below)
Repeats / intervalsIntervalsTrepeat blocksPreserved
Free-ride segmentsFreeRideopen stepPreserved as an untargeted step

Gotchas ​

Watts vs. % FTP. Zwift stores power as % FTP; Garmin Connect stores watts, so every % FTP target is converted with your FTP: watts = round(% / 100 × FTP). There is no assumed default — without an FTP the conversion stops with an error instead of writing wrong watts.

  • Editor: set your FTP in Athlete; export and "Send to Garmin" use the FTP of the workout's sport from your profile.
  • CLI: pass it with --ftp, e.g. kaiord convert -i workout.zwo -o workout.gcn --ftp 250 or kaiord garmin push -i workout.zwo --ftp 250.
  • SDK: createGarminWriter({ ftpWatts: 250 }) instead of garminWriter.
  • MCP: pass ftp to kaiord_convert or kaiord_garmin_push.

At an FTP of 250 W, Power="0.85" becomes a 213 W target.

Ramps. Zwift ramps (Warmup, Cooldown, Ramp) become a ranged power target, and both bounds are converted with the same FTP: at 250 W, a 50–75 % warm-up becomes a 125–188 W target. Cooldown blocks and other descending ramps (PowerLow above PowerHigh) are converted the same way: a 75–50 % cooldown also becomes a 125–188 W target, because a Garmin Connect range holds its two bounds, not the direction of the ramp. A ramp needs an FTP just like a steady-state block.

How does it get on my watch? A .gcn file is Garmin Connect's JSON, not a device file. Push it to your account with kaiord garmin push (via @kaiord/garmin-connect) or use the Editor's Garmin sync; Garmin Connect then syncs it to your device.

Heart-rate and cadence. Zwift workouts are power-based, so ZWO rarely carries heart-rate or cadence targets to bring across. Any that exist are mapped where Garmin Connect supports them.