Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

GPX to FIT Route Converter

This Val Town HTTP val converts a GPX route or track into a Garmin-compatible .fit course file.

Inspired by the Fit Route Converter site as well as the more advanced Tobias Fork (Github) of Fit Route Converter

Endpoint

Deployed endpoint:

https://gwoods22--e235379a275711f086ff569c3dd06744.web.val.run/convert

Send a POST request with either a multipart GPX upload or raw GPX XML.

Multipart upload

curl -L \ -F "file=@route.gpx" \ -o route.fit \ "https://gwoods22--e235379a275711f086ff569c3dd06744.web.val.run/convert"

Raw GPX body

curl -L \ -X POST \ -H "Content-Type: application/gpx+xml" \ --data-binary @route.gpx \ -o route.fit \ "https://gwoods22--e235379a275711f086ff569c3dd06744.web.val.run/convert"

Average speed

Timing is included in the generated FIT file. By default, the route is timed at 20 kph. Override that by sending X-Average-Speed with a positive numeric kph value:

curl -L \ -H "X-Average-Speed: 25" \ -F "file=@route.gpx" \ -o route.fit \ "https://gwoods22--e235379a275711f086ff569c3dd06744.web.val.run/convert"

Invalid values, such as 0, negative numbers, or non-numeric strings, return a 400 JSON error.

How It Works

The HTTP handler reads the GPX file, logs the incoming filename, parses the GPX track or route points, computes cumulative distance with the haversine formula, and assigns timestamps based on distance and average speed.

The converter then writes a minimal FIT course file containing:

  • file ID and course metadata
  • lap summary with total distance, timer time, and elevation gain/loss when elevation exists
  • timed record points with latitude, longitude, distance, and optional altitude
  • timer start/stop events
  • course-point turn hints derived from bearing changes

The FIT encoder writes the file header, message definitions, data records, and CRC trailer directly in TypeScript.

Credit

The conversion logic is adapted from Nick Holloway's fit-route project, especially the FIT encoder files in:

https://gitlab.com/nwholloway/fit-route/-/tree/main/public/js/fit

This project ports the relevant browser-side ideas into a Val Town HTTP val.