Cookies for analytics and advertising
We use cookies for analytics and advertising, both sent to Google. Refusing changes nothing you can see.Read the privacy page
Converting TCX to CSV gives you one row per recorded trackpoint, carrying latitude, longitude, elevation and timestamp. It does not give you heart rate, cadence, power, calories or lap totals — those live in parts of the TCX this conversion does not read, and a table that omitted them quietly would be worse than one that says so.
Up to 100 files at once. Mixed formats are fine.
They convert one after another and download together as a ZIP.
TCX to CSV
Training Center XML was designed by Garmin to describe a training session. Its structure says so: a `TrainingCenterDatabase` holds Activities, each Activity holds Laps, each Lap declares a duration, a distance, a calorie figure, an average and maximum heart rate and a trigger reason, and only then does it hold a Track of individual points. The route is one recorded stream among several, and arguably not the most important one.
The CSV produced here takes that last part and nothing else. Every trackpoint that has a position becomes a row of latitude, longitude, elevation and time, in order, and the entire training layer above it is discarded. If you came for a route you can plot or measure, that is a clean result. If you came for the workout, most of what you wanted stayed in the file, and the rest of this page is about exactly which parts.
A Trackpoint in a TCX can carry a `<Time>`, a `<Position>` with latitude and longitude in separate child elements, an `<AltitudeMeters>`, a `<DistanceMeters>` running total, a `<HeartRateBpm>`, a `<Cadence>` and an `<Extensions>` block. Four of those are read: the time, the two halves of the position and the altitude. They become the `time`, `latitude`, `longitude` and `elevation` columns, with a `type` column that always says `track` and a `name` column carrying the activity's sport.
The omission most likely to surprise is `<DistanceMeters>`. It is present on most trackpoints written by a Garmin device, it is the odometer reading at that instant, and it is not read — so the CSV contains no cumulative distance even though the source had one on every row. Recomputing it from consecutive coordinates gives a slightly different answer, because the device blends wheel or footpod data into its own figure and a coordinate difference cannot.
Heart rate is a direct child of the Trackpoint, written as `<HeartRateBpm><Value>148</Value></HeartRateBpm>`. Cadence is a sibling of it. Running cadence, watts and instantaneous speed are one level further down, in an `<Extensions>` block using Garmin's activity extension namespace. All of them are per-sample values sitting alongside the coordinates that do come through.
The reason none of them appear is that this converter reads every GPS format through one shared shape — a point with a position, an optional elevation and an optional time — and writes each output from that shape. It is what lets the same code turn a TCX into a GPX, a KML or a table without four separate implementations, and the price is that anything outside those four values is invisible to it. That is a real limitation and not a temporary one, which is why it is stated here rather than buried in a caveat.
Laps are flattened. The trackpoints of every lap in an activity are concatenated into one continuous run of rows, so twelve four-hundred-metre repeats come out looking exactly like one uninterrupted six-kilometre effort. Nothing in the CSV marks where one lap ended, and no column records which lap a row belonged to.
The summaries go with them, and they are the part a coach would miss most: `TotalTimeSeconds`, `DistanceMeters`, `MaximumSpeed`, `Calories`, `AverageHeartRateBpm` and `MaximumHeartRateBpm` are all declared per lap in the source and none reaches the table. If lap structure is what you are analysing, read the TCX directly — it is plain XML and the lap elements are near the top of each activity, which makes them unusually easy to pull out with a short script.
A Trackpoint with no `<Position>` is dropped, and this accounts for almost every case where the CSV has fewer rows than the file has trackpoints. It is not corruption. A watch samples heart rate once a second regardless of satellite lock, so a session that starts indoors, passes under a bridge or runs through a covered street writes perfectly valid trackpoints that carry a time and a heart rate and no coordinates.
A treadmill or turbo-trainer session is the extreme case: every trackpoint lacks a position, so nothing at all can be written and the conversion reports that no track or waypoints were found. That message is accurate — the file genuinely contains no geometry — and it is the clearest possible signal that a route-shaped output was never going to work for this workout.
A TCX activity has no name. What it has is a `Sport` attribute on the `<Activity>` element, and the schema restricts it to `Running`, `Biking` or `Other`. That value becomes the `name` column, repeated on every row, because it is the only human-readable identifier the format offers.
The title you see in Garmin Connect or Strava — "Tuesday intervals", "Commute home" — is not in the TCX at all; it belongs to the platform's database rather than to the export. So a folder of TCX files converted one after another produces tables whose name columns say `Running` over and over, and telling them apart afterwards depends on the filenames and on the `time` column rather than on anything inside the data.
The `<Time>` element is copied verbatim into the `time` column with no reformatting, because ISO 8601 is already a standard and rewriting it would only impose a preference. Garmin devices typically write UTC with a trailing `Z`, and some write fractional seconds, so `2026-03-14T06:41:07.000Z` arrives with its milliseconds intact.
That has two practical effects. Sorting by the column works as plain text, since ISO 8601 orders lexically, which means a merged table of several sessions sorts correctly even in a tool that has not recognised the column as a date. And the times are in UTC, so a 6:41 row is a 07:41 start in central European winter — a difference big enough to move an early session into the previous day when you group by date.
If the analysis you have in mind is about heart rate, power or cadence, this conversion will not get you there and no amount of post-processing will recover values that were never written. The TCX itself is the shortest path: it is plain XML, the elements are consistently named, and a dozen lines of Python with `xml.etree` will pull `HeartRateBpm` and `Time` into a dataframe with the coordinates alongside them.
The other honest option is the platform that produced the file. Garmin Connect, Strava and TrainingPeaks all export per-activity data with the sensor columns already flattened, and their exports include values the TCX does not hold at all — normalised power, training load, the device's own zone calculations. Converting a TCX is the right move when you want the route; it is the wrong move when you want the workout.
`<AltitudeMeters>` is copied into the elevation column exactly as the device wrote it: metres, no rounding, no correction. On a watch with a barometric altimeter that is a pressure reading calibrated at the start of the session. On one without, it is the satellite altitude solution, which is the noisiest number a GPS produces and drifts by tens of metres while the receiver stands still.
The figure a training platform shows you is frequently not that number. Elevation correction — looking each position up in a terrain model and substituting the surveyed height — is a common default, and it produces a smoother profile and a different total ascent. Neither value is wrong; they measure different things. So expect a discrepancy when you compare the converted table against the app, and expect it to be larger on a windy day or as a weather front passes, because that is exactly what an uncalibrated barometer responds to.
The XML is parsed and the rows are written in your browser. No request carries the TCX anywhere, which can be confirmed in the network panel while a conversion runs, and the page continues to work with the connection switched off once it has loaded.
That matters more for training data than the file extension suggests. A TCX is a minute-by-minute record of where somebody was, how hard they were working and how their heart responded, which is health data in every sense that matters and location data on top of it. A converted CSV is the same information in a form anybody can read at a glance, so it is worth keeping under the same care as the original rather than treating it as a derived scratch file.
| TCX | CSV | |
|---|---|---|
| Full name | Training Center XML | Comma-Separated Values |
| File extension | .tcx | .csv |
| Media type | application/vnd.garmin.tcx+xml | text/csv |
| First published | 2007 | 1972 |
| Published by | Garmin | — |
| Specification | Training Center XML | RFC 4180 |
| Licensing | Published, not standardised | Open standard |
| Standing today | Current | Current |
| Opens in a browser | No browser | No browser |
| Considered instead | GPX, FIT | XLSX, JSON, Parquet |
The usual programs do not overlap: TCX opens in Garmin Connect, Strava and TrainingPeaks, CSV in Microsoft Excel, LibreOffice Calc and pandas — so whoever receives the result needs something from the second list.
The two are aimed at different work: TCX at fitness tracking, CSV at moving data between programs. That is worth weighing before converting, because the reason one exists is usually the reason the other is awkward.
TCX is Garmin's format, published in 2007. The specification is Training Center XML, and it is worth reading if the file has to outlive the tool that wrote it.
CSV dates from 1972, specified as RFC 4180. Microsoft Excel, LibreOffice Calc and pandas all read it.
CSV was published in 1972 and TCX in 2007. The older one is generally the safer file to hand to somebody; the newer one usually does the job in fewer bytes.
No. This conversion runs entirely inside your browser, so the file never leaves your device. You can confirm it yourself: open the network tab of your browser's developer tools and convert something. You will see the page load, plus the analytics and advertising the site is paid for with — and nothing carrying your file.
No. `
None of them. Cadence and power live in the Trackpoint extensions, calories are a per-lap summary, and the conversion reads neither the extensions nor the lap totals.
No. A TCX groups its trackpoints into laps, and those boundaries are flattened into one continuous sequence of rows. A five-lap track session and a five-kilometre steady run produce structurally identical output.
Because trackpoints without a position are dropped. A watch records a heart-rate sample every second whether or not the GPS has a fix, and those samples are legal TCX and cannot become a row in a table of coordinates.
The Sport attribute of the activity — `Running`, `Biking` or `Other` in most files — repeated on every row of that activity. It is the only label a TCX activity carries.
Yes. Each Activity becomes its own run of rows, distinguished by the name column, in the order the file lists them.