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:

  1. A header — 12 or 14 bytes, carrying the protocol and profile versions, the size of the data that follows, and the ASCII marker .FIT at offset 8.
  2. A stream of definition messages, each claiming one of 16 local type slots and describing the fields of a message type.
  3. A stream of data messages, each naming a local slot and carrying values in exactly the layout that slot's current definition describes.
  4. 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

MeasurementValue
Files68,068 (602 MB)
Data points recovered149,952,107
Message types that carry data79
Manufacturers46
Distinct profile versions241
Files that redefine a local type52.85%
Files that will not parse4

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.