// gc-export-daily-hr // v4 (2025-11-14) function gcExportDailyHeartRate() { let loc = window.location.href let connectURL = "https://connect.garmin.com"; //https://connect.garmin.com/modern/heart-rate/2025-03-10/0 let dailyURL = "https://connect.garmin.com/modern/heart-rate/" if (loc.indexOf(connectURL) != 0 || typeof jQuery === "undefined" || !localStorage.token) { alert( `You must be logged into Garmin Connect to run this script. Log into ${connectURL} and try again.` ); return; } // Garmin Connect uses jQuery, so it's available for this script jQuery("#_gc-daily_hr_modal").remove(); _gcExportDailyHeartRate(); function _gcExportDailyHeartRate() { let today = new Date(); if (loc.indexOf(dailyURL) == 0) { let todayStr = loc.replace(dailyURL, ""); const dateRegExp = /^(\d\d\d\d)-(\d\d)-(\d\d)/; const match = todayStr.match(dateRegExp); if (match && match.length !== 0) { today = new Date(match[1], match[2]-1, match[3]); } } let defaultStartDate = formatDate(today); let startDate = promptDate( `Export Garmin Connect Daily Heart Rate Enter date to export (YYYY-MM-DD): `, defaultStartDate ) if (!startDate) { return; } startDate = formatDate(startDate); let xhr = new XMLHttpRequest(); const token = document.querySelector('meta[name="csrf-token"]') if (token) { xhr.open('GET', `https://connect.garmin.com/gc-api/wellness-service/wellness/dailyHeartRate?date=${startDate}`); xhr.setRequestHeader('connect-csrf-token', token.content); } else { xhr.open('GET', `https://connect.garmin.com/wellness-service/wellness/dailyHeartRate?date=${startDate}`); xhr.setRequestHeader('Authorization', 'Bearer '+JSON.parse(localStorage.token).access_token); xhr.setRequestHeader("NK", "NT") xhr.setRequestHeader('di-backend', 'connectapi.garmin.com') } xhr.onload = function () { if (xhr.status !== 200) { alert(`Error exporting data: ${xhr.status} ${xhr.statusText}\n\nMake sure you are logged into Garmin Connect and try again.`) return; } let obj = JSON.parse(xhr.response) addDialog(obj, startDate) }; xhr.onerror = function(error) { alert(`Error exporting data: ${error}\n\nnMake sure you are logged into Garmin Connect and try again.`) } xhr.send() } function formatDate(date) { let d = new Date(date), month = '' + (d.getMonth() + 1), day = '' + d.getDate(), year = d.getFullYear(); if (month.length < 2) month = '0' + month; if (day.length < 2) day = '0' + day; return [year, month, day].join('-'); } function formatDateAndTime(date) { let d = new Date(date), month = '' + (d.getMonth() + 1), day = '' + d.getDate(), year = d.getFullYear(); if (month.length < 2) month = '0' + month; if (day.length < 2) day = '0' + day; return `${[year, month, day].join('-')} ${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`; } function promptDate(str, def) { while (true) { const val = prompt(str, def); if (!val) { return val; } const dateRegExp = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; const match = val.match(dateRegExp); if (!match || match.length == 0) { continue; } let d; d = new Date(match[1], match[2]-1, match[3], 0, 0, 0); // console.log(d) return d; } } function getCSV(data) { let csv = `HR Date/Time,HR Value,Summary Description,Summary Value\n`; csv += `,,Date,${data.calendarDate}\n`; csv += `,,Min HR,${data.minHeartRate}\n`; csv += `,,Max HR,${data.maxHeartRate}\n`; csv += `,,Resting HR,${data.restingHeartRate}\n`; csv += `,,Resting HR (7-day average),${data.lastSevenDaysAvgRestingHeartRate}\n`; csv += `,,,\n`; for (let i = 0; i < data.heartRateValues.length; i++) { var val = data.heartRateValues[i]; var d = new Date(0); d.setUTCSeconds(val[0] / 1000); if (val[1] === null || val[1] === undefined) { csv += `${formatDateAndTime(d)},,,\n`; } else { csv += `${formatDateAndTime(d)},${val[1]},,\n`; } } return csv; } function addDialog(data, startDate) { addCSS(); jQuery("#_gc-daily_hr_modal").remove(); const output = JSON.stringify(data, null, 2); if (data.heartRateValues === null) { alert(`No heart rate data found for ${data.calendarDate}`); return; } const csv = getCSV(data); jQuery('body').append(`
X

Garmin Daily Heart Rate: ${startDate}

CSV (formatted):

You probably want to press this button 👉 Download CSV

JSON (raw, for advanced use only):

Download JSON
`); jQuery("#_gc-hr-close").click(function() { jQuery("#_gc-daily_hr_modal").remove(); return false; }); jQuery("#_gc-hr_copy").click(function() { let el = document.getElementById("_gc-hr_textarea"); el.select(); document.execCommand('copy'); jQuery("#_gc-hr-copied").show(); return false; }); jQuery("#_gc-hr-csv_copy").click(function() { let el = document.getElementById("_gc-hr-csv_textarea"); el.select(); document.execCommand('copy'); jQuery("#_gc-hr-csv-copied").show(); return false; }); } function addCSS() { // based on https://jsfiddle.net/kumarmuthaliar/GG9Sa/1/ let styles = ` ._gc-hr-modalDialog { position: fixed; font-family: Arial, Helvetica, sans-serif; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.8); z-index: 99999; // opacity:0; -webkit-transition: opacity 400ms ease-in; -moz-transition: opacity 400ms ease-in; transition: opacity 400ms ease-in; } ._gc-hr-modalDialog > div { width: 600px; position: relative; margin: 20px auto; padding: 5px 20px 13px 20px; border-radius: 10px; background: #eee; /*background: -moz-linear-gradient(#fff, #999); background: -webkit-linear-gradient(#fff, #999); background: -o-linear-gradient(#fff, #999);*/ } ._gc-hr-close { background: #606061; color: #FFFFFF; line-height: 25px; position: absolute; right: -12px; text-align: center; top: -10px; width: 24px; text-decoration: none; font-weight: bold; -webkit-border-radius: 12px; -moz-border-radius: 12px; border-radius: 12px; -moz-box-shadow: 1px 1px 3px #000; -webkit-box-shadow: 1px 1px 3px #000; box-shadow: 1px 1px 3px #000; } ._gc-hr-close:hover { background: #00d9ff; } ._gc-hr-btn, ._gc-hr-btn:hover, ._gc-hr-btn:visited, ._gc-hr-btn:active { color: #fff; background-color: #337ab7 !important; border-color: #2e6da4 !important; } ._gc-misc-btn, ._gc-misc-btn:hover, ._gc-misc-btn:visited, ._gc-misc-btn:active { color: #fff; text-decoration:none; background-color: #6c757d; border-color: #6c757d; display: inline-block; margin-bottom: 0; font-weight: 400; text-align: center; white-space: nowrap; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; border-radius: 4px; } #_gc-hr_textarea, #_gc-hr-csv_textarea { font-family: "Lucida Console", Monaco, Monospace } ` jQuery("#_gc-hr_styles").remove(); let styleSheet = document.createElement("style") styleSheet.type = "text/css" styleSheet.id = "_gc-hr_styles" styleSheet.innerText = styles document.head.appendChild(styleSheet); } } gcExportDailyHeartRate();