Hello @David and @app4g, of course, let me provide some examples. I’ll try to explain this as clearly as I can, though it’s a somewhat complex topic.
That is exactly the behavior I expected from Intervals.icu. At this point, Intervals.icu is my final command center for data management — regardless of where I record a workout, I always make sure it lands in Intervals.icu first.
Sometimes it’s necessary to fix corrupted data, remove artifacts, or add data that wasn’t captured by a specific app or device. For example, Stryd does not include calories in the FIT files exported from PowerCenter. So I import those files into Intervals.icu, edit or enrich the data, and then export the result to distribute it to Apple Health / COROS / Suunto.
To handle this, I’ve built custom flows in Intervals.icu for all my data sources (Stryd in this example, or any other), and I’ve created custom fields that mirror the standard FIT fields — specifically the data I want to preserve, since Intervals.icu rebuilds the FIT file from scratch on export.
However, here is the problem I’m running into. When I export a FIT file from Intervals.icu, all the custom fields I created — whether in the session, record, or other messages — are written as developer fields (0_dev_fields), following the FIT 2.0 developer data spec. While this is technically valid, it causes serious interoperability issues because FIT files are binary and field positions are fixed. Apps like COROS expect to find stride_length at a specific field definition number in the standard message structure — not in the developer fields section.
That’s why I proposed the improvement: if I name a custom field using the exact same name as a standard FIT field, the exporter should write it to its original, standard field position in the FIT message — rather than dumping it as a developer field prefixed with 0_dev_.
Of course, here are some concrete examples:
Stryd Running at file file:
timestamp position_lat position_long heart_rate cadence distance power **vertical_oscillation** **stance_time** **enhanced_speed** **enhanced_altitude** **stance_time_balance** **step_length** _x_Air Power_0_11 x_Form Power_0_8 x_Leg Spring Stiffness_0_9 x_Speed_0_5 x_Distance_0_6 x_Stryd Temperature_0_16 x_Stryd Humidity_0_15 x_Impact_0_24 x_Leg Spring Stiffness Balance_0_31 x_Vertical Oscillation Balance_0_32 x_Impact Loading Rate Balance_0_30_
standard ||dev_fields for stryd
Developer for stryd:
| developer_data_index |
field_definition_number |
fit_base_type_id |
field_name |
units |
native_mesg_num |
| 0 |
11 |
uint16 |
Air Power |
Watts |
|
| 0 |
8 |
uint16 |
Form Power |
Watts |
|
| 0 |
9 |
float32 |
Leg Spring Stiffness |
KN/m |
|
| 0 |
5 |
float32 |
Speed |
M/S |
bike_profile |
| 0 |
6 |
uint32 |
Distance |
Meters |
sdm_profile |
| 0 |
16 |
float32 |
Stryd Temperature |
C |
training_settings |
| 0 |
15 |
float32 |
Stryd Humidity |
% |
|
| 0 |
24 |
sint32 |
Impact |
Body Weight |
|
| 0 |
31 |
float32 |
Leg Spring Stiffness Balance |
Percent |
|
| 0 |
32 |
float32 |
Vertical Oscillation Balance |
Percent |
|
| 0 |
30 |
float32 |
Impact Loading Rate Balance |
Percent |
|
| 0 |
29 |
string |
Run Profile |
|
|
LAPS:
timestamp message_index event event_type start_time start_position_lat start_position_long end_position_lat end_position_long total_elapsed_time total_timer_time total_distance avg_power lap_trigger sport wkt_step_index enhanced_avg_speed
SESSION:
timestamp event event_type start_time start_position_lat start_position_long sport sub_sport total_elapsed_time total_timer_time total_distance avg_cadence avg_power total_training_effect first_lap_index num_laps trigger enhanced_avg_speed enhanced_max_speed** _x_Run Profile_0_29_
standard ||dev_fields for stryd
Now the same for Download intervals.icu file (processed):
Records:
timestamp position_lat position_long heart_rate cadence distance power enhanced_speed enhanced_altitude **x_stance_time_0_48** **x_step_length_0_49** x_Air Power_0_50 x_Form Power_0_51 x_Stryd Humidity_0_52 x_Impact_0_53 x_Impact Loading Rate Balance_0_54 x_Leg Spring Stiffness_0_55 x_Leg Spring Stiffness Balance_0_56 x_stance_time_balance_0_57 x_Stryd Temperature_0_58 x_Vertical Oscillation Balance_0_59 x_vertical_oscillation_0_60 x_RunCadence_0_61
SESSION:
timestamp message_index start_time sport sub_sport total_elapsed_time total_timer_time total_distance avg_heart_rate max_heart_rate avg_cadence max_cadence avg_power max_power total_ascent total_descent first_lap_index num_laps normalized_power training_stress_score intensity_factor threshold_power total_work enhanced_avg_speed enhanced_max_speed enhanced_avg_altitude enhanced_min_altitude enhanced_max_altitude x_RunCadence_0_12 x_training_load_peak_0_13 x_AverageStrydLSSkg_0_14 x_RATIODEEFICIENCIA_0_15 **x_avg_vertical_oscillation_0_16** x_avg_stance_time_0_17 x_avg_vertical_ratio_0_18 x_total_cycles_0_19 x_AvgStrydAirPowerPercent_0_20 x_AVGStrydTemp_0_21 x_AVGFormPower_0_22 x_AVGRITMOwork_0_23 x_AVGHR_0_24 x_IndiceRunning_0_25 x_DecouplingHR_0_26 x_CosteCardiaco_0_27 x_Economiadecarrera_0_28 x_RatioFormPower_0_29 x_RatioWorkReco_0_30 x_IntensidadRelativa_0_31 x_RatioGCTZancada_0_32 x_AVGStrydOscilacionVertical_0_33 x_RESUMENACTIVIDAD_0_34 x_AVGStrideLength_0_35 x_AVGEficienciaEF_0_36 x_AVGPotenciaMedia_0_37 x_RatedPerceivedExertion_0_38 x_AVGStrydHumidity_0_39
Concrete examples — comparing the original Stryd FIT file vs. the Intervals.icu export:
| Field |
FIT SDK Standard (field def #) |
Intervals.icu export |
stance_time |
Field 41 |
Dev field 48 → x_stance_time_0_48 |
step_length |
Field 85 |
Dev field 49 → x_step_length_0_49 |
stance_time_balance |
Field 84 |
Dev field 57 → x_stance_time_balance_0_57 |
| vertical_oscillation |
field 39 |
dev field 60 → x_vertical_oscillation_0_60 |
These are just a few examples from the standard FIT SDK field definitions. There are more — and beyond these generic ones, Stryd also has its own manufacturer-specific nominal fields, but that might be a separate and more specific conversation. Getting the standard fields mapped correctly would already be a massive improvement.
Why does this matter? Because these fields are written as developer fields instead of their native standard positions, they break compatibility with virtually every downstream consumer. The field name is preserved, but since it lives in the dev data section rather than at the expected binary offset, parsers like COROS, Suunto, and Apple HealthKit simply don’t recognize them.
I’m currently working around this in a Python script — I scan for the field name and remap it to the correct standard field definition on export — but it would be far cleaner and more reliable to have Intervals.icu handle this natively. The logic would be straightforward: if a custom field name matches a standard FIT SDK field name, write it to the standard field position instead of as a developer field.
I hope this is clear enough — happy to share actual FIT file samples directly if that helps. I have examples from running with Stryd, running without, and swimming — the same issue appears across all of them, as many fields that exist in the original file don’t survive into the Intervals.icu export in their standard form.
I’ve already tried to meet Intervals.icu halfway on this — when creating my custom fields, I deliberately used the exact same names as the standard FIT SDK fields (stance_time, step_length, stance_time_balance, etc.), hoping the exporter might recognize them and route them to their native field positions.
I understand that correctly mapping every possible standard field is probably the most complex part of this to implement. But the reverse logic should actually be quite simple: if a custom field name matches a known standard FIT SDK field, write it to the standard message field definition instead of the developer data section. No ambiguity, no guesswork — the SDK field definitions are fixed and well-documented.
It’s essentially a name lookup on export. The hard work of defining the fields has already been done on the user side.
This issue also affects swimming workouts — and arguably even more so, since it’s a less popular sport and therefore receives less attention from both device manufacturers and third-party apps. The result is that swim-specific FIT fields are even less likely to survive the export pipeline intact, and the ecosystem support for catching or correcting those gaps is much thinner.
Thank you very much for taking the time to look into this, David — it’s genuinely appreciated. I know this is a niche use case and the implementation details are not trivial, so I’m grateful you’re willing to consider it. As always, if you need actual FIT file samples to work with, I’m happy to share them directly. Keep up the great work with Intervals.icu — it truly is an outstanding platform.