Parse and encode FIT files in JavaScript and TypeScript. The parser supports files produced by Garmin, Polar, Suunto, and other FIT-compatible devices, including developer-defined data.
- Parse Node.js
Bufferand standardArrayBufferinputs. - Choose flat lists, nested activity data, or both output shapes.
- Convert speed, length, temperature, and pressure fields to preferred units.
- Decode developer fields while preserving record alignment when descriptions arrive after their definitions.
- Encode profile-agnostic FIT messages with validated field definitions and CRCs.
- Use ESM or CommonJS with bundled TypeScript declarations.
- Node.js 20 or newer
npm install fit-file-parserThe Promise API is the simplest way to parse a file:
import { readFile } from 'node:fs/promises'
import FitParser from 'fit-file-parser'
const content = await readFile('./activity.fit')
const parser = new FitParser({
mode: 'list',
speedUnit: 'km/h',
lengthUnit: 'km',
})
const data = await parser.parseAsync(content)
console.log({
sessions: data.sessions?.length ?? 0,
laps: data.laps?.length ?? 0,
records: data.records?.length ?? 0,
})import { readFile } from 'node:fs'
import FitParser from 'fit-file-parser'
readFile('./activity.fit', (readError, content) => {
if (readError) {
console.error(readError)
return
}
const parser = new FitParser()
parser.parse(content, (parseError, data) => {
if (parseError) {
console.error(parseError)
return
}
console.log(data)
})
})Parser errors are strings. parseAsync() rejects with the same value that the
callback API receives as its first argument.
All options are optional.
| Option | Values | Default | Behavior |
|---|---|---|---|
mode |
list, cascade, both |
list |
Controls whether primary activity collections are returned as root lists, nested data, or both. |
force |
true, false |
true |
Skips header and file CRC validation and enables supported best-effort field recovery. Structural header and data bounds are always validated. |
speedUnit |
m/s, km/h, mph |
m/s |
Converts speed-related fields. |
lengthUnit |
m, km, mi |
m |
Converts distance, altitude, and other length-related fields. |
temperatureUnit |
celsius, °C, kelvin, fahrenheit |
celsius |
Converts temperature fields. °C remains available as a legacy alias. |
pressureUnit |
bar, cbar, psi |
bar |
Converts pressure and tank-pressure fields. |
elapsedRecordField |
true, false |
false |
Adds elapsed_time and timer_time, in seconds, to records. |
force: true does not make arbitrary bytes a valid FIT file. Inputs that are
too short, have an invalid header size or signature, or declare data beyond the
available bytes are rejected in both modes.
The mode controls where sessions, laps, records, and related activity collections are exposed.
| Mode | Root lists | Nested under activity |
Default |
|---|---|---|---|
list |
Yes | No | Yes |
cascade |
No | Yes | No |
both |
Yes | Yes | No |
In cascade output, sessions contain their laps and laps contain their records and lengths. Other parsed FIT message collections remain available where the parser exposes them.
Every recognized message is also retained in file order in data.messages.
This additive index is useful for message kinds that historically exposed
only the last value at the root:
const workoutSteps = data.messages?.workout_step ?? []
const diveSummaries = data.messages?.dive_summary ?? []Existing root lists, cascade nesting, and last-message root properties remain unchanged.
Both parser methods accept:
- Node.js
Buffer ArrayBuffer
const parsed = await new FitParser().parseAsync(arrayBuffer)FIT producers may define custom fields outside the standard profile. The parser consumes every developer field's declared byte size so later messages stay aligned. If a field description is not available yet, that value is omitted. Subsequent values are decoded by name once the description appears.
FitEncoder writes FIT headers, message definitions, data messages, and CRCs.
It is profile-agnostic: callers provide profile message and field numbers,
base types, sizes, and values in their raw FIT representation. Applying FIT
scales and offsets is the caller's responsibility.
import { FitBaseType, FitEncoder } from 'fit-file-parser'
const encoder = new FitEncoder()
encoder.writeMessage(0, [
{
number: 0,
size: 1,
baseType: FitBaseType.Enum,
value: 6,
},
{
number: 4,
size: 4,
baseType: FitBaseType.Uint32,
value: FitEncoder.toFitTimestamp(new Date()),
},
])
const fitBytes = encoder.close()writeMessage(globalMessageNumber, fields, localMessageNumber?) accepts local
message numbers from 0 through 15. Definitions are emitted automatically and
reused until the shape assigned to that local number changes. close() returns
a Uint8Array.
The encoder also provides:
FitEncoder.string(value)for null-terminated UTF-8 field bytes.FitEncoder.toFitTimestamp(value)for FIT timestamps.FitEncoder.calculateCRC(bytes)for FIT-compatible CRC calculation.
Scalar 64-bit values use bigint. Strings, numeric arrays, and other
variable-length values use exact-size Uint8Array values. Invalid field
definitions or numeric ranges throw before a partial message is written.
The package includes TypeScript declarations and exports
FitParserOptions, FitEncoderField, and FitEncoderOptions.
ESM:
import FitParser, { FitBaseType, FitEncoder } from 'fit-file-parser'CommonJS:
const {
default: FitParser,
FitBaseType,
FitEncoder,
} = require('fit-file-parser')Run commands from the repository root.
| Command | Purpose |
|---|---|
npm ci |
Install locked dependencies. |
npm run build |
Build ESM and CommonJS output. |
npm test -- --run |
Run the complete test suite once. |
npm test -- --run test/<file>.ts |
Run a focused test file. |
npm run codegen |
Regenerate the Garmin profile and public types. |
npm run codegen:check |
Verify both generated files are current. |
npm run profile:audit |
Audit SDK profile coverage and private overlays. |
npm run corpus:check -- <path> |
Validate an external FIT corpus without file data. |
npm run lint |
Check lint and formatting rules. |
npm run fmt |
Apply the configured formatting rules. |
npm run type-check |
Run TypeScript without emitting files. |
npm run examples |
Build and regenerate checked-in example outputs. |
npm run check |
Run profile audit, lint, types, tests, and builds. |
Do not edit src/garmin_profile.generated.ts or src/fit_types.ts manually.
Update the pinned SDK, compatibility overrides, or a generator, then run
npm run codegen.
Repository-specific automation guidance is tracked in
.agent/README.md. More examples are available in the
examples directory, and release notes are in the
CHANGELOG.
This project started from work by
Pierre Jacquier. Thanks to
Mikael Lofjärd for
his early prototype, and to everyone in
CONTRIBUTORS.md.
MIT; see LICENSE.
Copyright 2019-present Dimitrios Kanellopoulos.