What is actually inside a .fit file
Most explanations of the FIT format describe the SDK. This one describes 68,068 real files — which is a different thing, and the difference is where FIT parsers go wrong.
Open your file
Drop your file here
any .fit file, to see this structure in your own data
No account. No upload. The file is read on this device and never leaves it.
Nothing has been sent anywhere. 0 requests to this site, for the page's own code.
The structure, briefly
A FIT file is four things in sequence:
- A header — 12 or 14 bytes, carrying the protocol and profile versions, the size of the data that follows, and the ASCII marker
.FITat offset 8. - A stream of definition messages, each claiming one of 16 local type slots and describing the fields of a message type.
- A stream of data messages, each naming a local slot and carrying values in exactly the layout that slot's current definition describes.
- A CRC.
Everything difficult about FIT follows from point 2 and 3 together: a data message is meaningless without its definition, and the definition can change.
The thing most parsers get wrong
Sixteen local slots is not many. A file with more than sixteen message types must reuse them, and a file that only has a few reuses them anyway — because devices write a definition, use it, and write a new one when the fields change.
Measured: 35,971 of 68,068 files (52.85%) redefine at least one local type, and they do it 566,605 times in total — an average of about 16 redefinitions per file that does it.
Redefinition is the majority case, not an edge case. Any parser that resolves a definition once and caches it produces silently wrong values for every record after the first replacement.
Byte order is per definition
Each definition message carries its own architecture byte. A single file can hold little-endian and big-endian definitions side by side. Assuming little-endian for the whole file — which is the natural thing to do, since most of it is — produces plausible garbage for the rest.
Absent is not zero
FIT marks a missing value with an invalid sentinel, and the sentinel depends on the
field's type. 0xFF for an unsigned byte, but 0x7F for a signed one,
and the …z types treat 0 itself as invalid. Treating
0xFF as universal writes 127 into every unset signed byte and −1 into things
that were simply not measured.
What is in the files, in aggregate
| Measurement | Value |
|---|---|
| Files | 68,068 (602 MB) |
| Data points recovered | 149,952,107 |
| Message types that carry data | 79 |
| Manufacturers | 46 |
| Distinct profile versions | 241 |
| Files that redefine a local type | 52.85% |
| Files that will not parse | 4 |
Measured 2026-08-23 against profile profile.xlsx:250986b:1407f:126m. messages are counted only when they produced a sample; a message the resolver does not map is invisible to this tool.
Density, which is why this format exists
149,952,107 data points from 602 MB is roughly 255 values per KiB. An Apple Health export runs closer to three. That density is the whole point of a binary format with per-message definitions, and it is why the densest file in the corpus holds 1,521,554 measurements in 4.2 MB.