Skip to content

Instantly share code, notes, and snippets.

Last active October 9, 2015 14:57
Show Gist options
  • Save runspired/3526501 to your computer and use it in GitHub Desktop.
Save runspired/3526501 to your computer and use it in GitHub Desktop.
Demonstration of how to port your Garmin Connect data to Nike+ using If you have any questions, feel free to tweet me @runspired
As of 5/13/2013 3:06PM Central Time this script is working. Tweet @runspired to report malfunctions.
Use the Javascript console in Google Chrome and the tcx2nikeplus converter located here:
This will take some time to run but will port all of your garmin data to nike+.
you will need to set all of your garmin plus workouts to public for this to work properly. The first function converts all of your garmin connect data to a public privacy setting after it gathers the activity urls, if you would like to return your activities to private, tweet me @runspired and I'll show you how to do so.
//------------------------- Garmin Connect set privacy to public and gather activity urls PART ---------------------------
Run this in chrome's javascript console
on Garmin Connect's activities page. By opening the console, copy pasting this
function, and hitting the return key.
function garminWalker() {
var walkerInstance = this
, activities = new Array()
, sendRequest = function(index) {
type : 'POST'
, url : '' + activities[index].match(/^\/activity\/(\d+)\/?$/ )[1]
, processData : false
, data : 'value=public&_='
, success : function(m,s,r) { if( index == 0 ) console.log( '["' + activities.join('","') + '"]' ); else sendRequest( index-1); }
, makePublic = function() {
console.log( "making all activities public" );
sendRequest( activities.length - 1 );
, getActivities = function() {
console.log( "gathering activities from this frame" );
var wrappers = document.getElementsByClassName('activityName')
, index = wrappers.length;
var next = document.getElementsByClassName('rich-datascr-button')[3];
if( next.hasAttribute('onclick') && next.getAttribute('onclick') != '')
wait( wrappers[0], 0 );
, wait = function( compare , count ) {
//infinite recursion preventer
var count = !!count? count : 0;
//ensure the necessary action is taking place
if( count == 30 )
//hard cap on recursion
if( count > 100 )
//continue recursion if the val has not changed
if( document.getElementsByClassName('activityName')[0] === compare )
setTimeout( (function(){ wait(compare,++count); }), 100);
//start the script running
new garminWalker();
After the page stops loading new results and sets all activities to public it will wait ~5s and then print
a string (probably very long) that looks something like
Copy this string and paste it somewhere you can copy it from again in a moment.
//------------------------- garmin2nike PART ---------------------------
uploader, run this in Chrome's javascript console on the garmin2nike+ page located
by copy pasting it into the console and hitting return.
This Script will take a VERY long time to complete, just leave the window open and let it run.
Periodically some of the activities will fail to upload, the most common reason is the lack
of a GPS track or not enout (at least 3) data points on the GPS track.
The script will continue executing, you can manually go look at the activities that failed if you
would like to verify the reason they didn't convert. There is (unfortunately) nothing I can do about
Garmin data being bad.
(function() {
function tcxConverter(u,p,a) {
var requestURL = ''
, baseUrl = ""
, activities = a || false
, username = u || ''
, password = p || ''
, inputElement = document.getElementById('garminActivityId')
, submitButton = document.getElementById('ext-gen38')
, buildFormString = function(id){
var text = ''
, EOL = "\r\n"
, boundary = "------WebKitFormBoundarywZcuQVboZzlXS7Yv";
text += boundary + EOL + 'Content-Disposition: form-data; name="fsGarminId-checkbox"' + EOL + EOL + 'on' + EOL;
text += boundary + EOL + 'Content-Disposition: form-data; name="garminActivityId"' + EOL + EOL + id + EOL;
text += boundary + EOL + 'Content-Disposition: form-data; name="nikeEmail"' + EOL + EOL + username + EOL;
text += boundary + EOL + 'Content-Disposition: form-data; name="nikePassword"' + EOL + EOL + password + EOL;
text += boundary + EOL + 'Content-Disposition: form-data; name="chkSaveCookies"' + EOL + EOL + 'on' + EOL;
text += boundary + EOL + 'Content-Disposition: form-data; name="clientTimeZoneOffset"' + EOL + EOL +(-1*(new Date()).getTimezoneOffset())+ EOL;
text += boundary + '--' + EOL;
return text;
, ajax = function(o) {
var xhr;
if( window.XMLHttpRequest )
xhr = new XMLHttpRequest();
else {
console.log("error establishing server connection");
return false;
//ensure readiness
xhr.onreadystatechange = function() {
if(xhr.readyState < 4) {
if(xhr.status !== 200) {
o.error( xhr );
// all is well
if(xhr.readyState === 4)
}; o.type , o.url , true );
xhr.send( );
return false;
, sendRequest = function(index) {
console.log("transferring activity: "+activities[index]);
type : 'POST'
, url : requestURL
, contentType : 'multipart/form-data; boundary=----WebKitFormBoundarywZcuQVboZzlXS7Yv'
, data : buildFormString(activities[index])
, success : function(xhr ) { = JSON.parse( xhr.responseText );
if( == false ) {
console.log("Transfer failed for "+activities[index],;
if( index > 0 ) sendRequest( index-1 );
else console.log("all activities transferred");
, error : function(xhr) { console.log("aborting process, unable to reach server");}
sendRequest( activities.length - 1 );
this.garmin2nike = function(u,p,a) { new tcxConverter(u,p,a); };
Enter your Nike+ email and password.
after you get the string from running the script on Garmin Connect,
type the line below and copy paste the string from Garmin Connect
where it says 'stringFromPartOne'. Remove the outter layer of quotes (ie it should
look something like ["/url","/url"] not "["/url","/url"]"), make sure you entered your
Nike+ email and password, and hit return.
garmin2nike( nikePlusEmailInQuotes , nikePlusPasswordInQuotes , stringFromPartOne );
Sit back and let the script do it's thing, note, error messages may occassionally pop up that you will need to dismiss,
such as "Error message: 1 is smaller than the minimum (3): number of points (1)"
This is something to do with the script located here not being able to convert the data for a particular workout, not
something I can change.
Copy link

Trying to run the second script (on Garmin Connect) I got this error message: "Uncaught TypeError: Object [object Window] has no method 'activityUrls'".

Any hint?

Copy link

I got the same error message as the previous commenter. I would LOVE for this to work... any help??

Copy link

@serenamichelle Still need help? (for some reason I didn't get a notification for your comment, and just happened to stumble past it)

Copy link

Im having the same issue: TypeError: Object [object global] has no method 'activityUrls'

Copy link

cohengal commented May 8, 2013

exact same issue here, not sure whats missing or where it went wrong. Would love to import all my garmin activity's to nike+. great idea !

Copy link

@cohengal @ssamuel68 @serenamichelle I believe this issue has now been fixed, easiest way to reach me if it isn't is on twitter @runspired

Copy link

I've completely rewritten both scripts to make them more future proof.

Copy link

Hi I am trying to get run garmin2nike PART of your script I was able to retreive all the garmin activities from my garmin.connect account. when I run the second script from chrome console. I get the following error: SyntaxError: Unexpected token ILLEGAL

I not sure what this error means. I try to hard my username and password and enter the the activitiy
garmin2nike( nikePlusEmailInQuotes , nikePlusPasswordInQuotes , stringFromPartOne ) function
I still get the error.

Can you et me know what this is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment