Export Google Fit Daily Steps, Weight and Distance to a Google Sheet

Updated on Friday, April 28, 2023

Google Fit Daily Step Export

Google Fit is a great way to keep track of your daily step count without needing to carry a Fitbit or other dedicated tracker. It's not easy to get that data out though, as far as I can tell the only way is Google Takeout which is not made for automation. Luckily there is an API and you can do almost anything with Google Sheets.

If you're looking to export your step count, weight and distance this post has everything you need, just follow the instructions below to get your spreadsheet up and running. This is also a good primer on using OAuth2 with Google Apps Script and should be a decent starting point for a more complex Google Fit integration. If you have any questions or feedback please leave a comment below.

To get started you need a Google Sheet, an apps script project attached to the sheet and a Google API Project that will provide access to the Fitness API. That might sound intimidating but it should only take a few minutes to get everything up and running.

In Google Drive create a new spreadsheet and call it whatever you like. Rename the first tab to 'Metrics'. Enter 'Date' in cell A1, 'Steps' in B1, 'Weight' in C1 and 'Distance' in D1. To grab history as well create another tab called 'History' with the same headers. Next select 'Script editor...' from the Tools menu which will open a new apps script project.

Give the apps script project a name and then select 'Libraries...' from the Resources menu. Next to 'Add a library' enter 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF and click Add. This will find the Google OAuth2 library. Choose the most recent version (24 at the time of writing) and click Save. Then select 'Project properties' from the File menu and make a note of the Script ID (a long series of letters and numbers).

Open the Google API Console. Create a new project and name it something like 'Google Fit Sheet'. From the Dashboard click Enable APIs and Services and find and select the Fitness API. Then go to Keys and create an OAuth Client ID. You'll be asked to create a consent screen, the only field you need to enter is the product name (i.e. 'My Fit App'). Then choose Web Application as the application type. You need to set the name and the authorized redirect URL. The redirect URL is https://script.google.com/macros/d/{SCRIPTID}/usercallback replacing {SCRIPTID} with the actual Script ID you made a note of above. After adding this make a note of the Client ID and Client Secret.

Go back to the apps script project and paste the code below into the Code.gs window:

Right at the top of the code there are spaces to enter the Client ID and Client Secret from the API Console. Enter these and save the project.

Switch back to your Google Sheet and reload. After reloading there will be a Google Fit menu item. First select Authorize... You'll get a screen to authorize the script and then a sidebar with a link. Click the link to authorize the script to access your Google Fit data. You can then close the sidebar and select Get Metrics for Yesterday from the Google Fit menu. You should see a new row added to the spreadsheet with yesterday's date and fitness data.

The final step is to automate pulling in the data. Go back to the apps script project and select Current project's triggers from the Edit menu. Add a trigger to run getMetrics() as a time driven day timer - I recommend between 5 and 6am. You can also click notifications to add an email alert if anything goes wrong, like your Google Fit authorization expiring (in which case you just need to come back and authorize from the Google Fit menu again.

At this point you're all set. Every day the spreadsheet will automatically update with your step count from the day before. You can add charts, moving averages, export to other systems, pull in your weight or BMI, etc. I want to add a seven day moving average step count to this blog somewhere as a semi-public motivational tool... watch this space.

Note that weight will be blank in the spreadsheet for days with no weight data. Google Fit doesn't return the last known weight, only the known value for days where an update was recorded.

If you are looking to extend this sample to other data types then this API explorer page is very helpful for finding data types that the API documentation doesn't list.

A couple of times working on this script I got my authorization in a bad state and started getting a 400 error response from the API. If this happens run your Google Fit app, click the Profile icon at the bottom and then the Settings icon at the top right. Click Manage connected apps and then disconnect the script from Google Fit. Finally run the Reset Settings option from the menu in the sheet and then authorize again.

I updated this post on Jan 21, 2019 to extend the sample to handle weight and distance as well as steps. I also improved the history function to handle many days in one API call rather than a quick hack I added earlier that pulled a day at a time. I'd recommend using the code above rather than anything included in comments below (at least comments before this update).

Add your comment...

More Google Apps Script Projects

(All Code Posts)

Comments

Simon

Does anybody know how to prevent the authorization from expiring?

With "Publishing Status" set to "Testing" in the GCP console, the credentials expire after 7 days. If I publish the app and the status becomes "In Production" (without going though any verification steps), the credentials expire within a few hours.

The 7-day expiry seems to be expected behavior (see "How to add authentication to a Google apps script without it expiring?" on stackoverflow) but I haven't found a way around it without having to go through Google's verification process.

Simon

Here's my code for fetching sleep data. Make sure you add the appropriate scope (fitness.sleep.read), and then trigger getSleepData().

```

// Populate sleep data

function getSleepData() {

const numDaysToGet = 10;

const tabName = 'GFitImportSleep';

const timezone = 'America/Los_Angeles';

getDailySleepData(numDaysToGet, 0, tabName, timezone);

}

// Format string number of milliseconds using provided timezone and format string

function toDate(millis, tz, format) {

var d = new Date(parseInt(millis));

return Utilities.formatDate(d, tz, format);

}

/*

Load sleep sessions and overwrite the given tab with the data.

First line of output is a description, second line is headers.

*/

function getDailySleepData(fromDaysAgo, toDaysAgo, tabName, timezone) {

let start = new Date();

start.setHours(0,0,0,0);

start.setDate(start.getDate() - fromDaysAgo);

let end = new Date();

end.setHours(23,59,59,999);

end.setDate(end.getDate() - toDaysAgo);

const url = `https://www.googleapis.com/fitness/v1/users/me/sessions?startTime=${start.toISOString()}&endTime=${end.toISOString()}&activityType=72`;

const fitService = getFitService();

const response = UrlFetchApp.fetch(url, {

headers: {

Authorization: 'Bearer ' + fitService.getAccessToken()

},

'method' : 'GET',

});

const json = JSON.parse(response.getContentText());

Logger.log(json);

let ss = SpreadsheetApp.getActiveSpreadsheet();

let sheet = ss.getSheetByName(tabName);

sheet.clear({ formatOnly: false, contentsOnly: true });

sheet.appendRow(["This sheet is generated via Apps Script using the GFit API. Use the 'Google Fit' menu to regenerate manually. If seeing authorization errors, click 'Reset Settings' and then 'Authorize' in that menu."])

sheet.appendRow(["startTimeMillis", "start time", "endTimeMillis", "end time", "end date", "duration (h)", "data"]);

for(var s = 0; s < json.session.length; s++) {

const session = json.session[s];

const duration_m = (session.endTimeMillis - session.startTimeMillis)/(1000 * 60 * 60);

sheet.appendRow(

[session.startTimeMillis,

toDate(session.startTimeMillis, timezone, 'h:mm:ss a MM/dd/yy z'),

session.endTimeMillis,

toDate(session.endTimeMillis, timezone, 'h:mm:ss a MM/dd/yy z'),

toDate(session.endTimeMillis, timezone, 'MM/dd/yy'),

duration_m,

session]);

}

}

```

Baran Gulmez

i wanted to add sleep data too but i just couldn't find the documentation of google for below lines anywhere. how can i find sleep data's dataTypeName and dataSourceId ?

{

"dataTypeName": "derived:com.google.step_count.delta",

"dataSourceId": "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps"

},

{

"dataTypeName": "com.google.weight.summary",

"dataSourceId": "derived:com.google.weight:com.google.android.gms:merge_weight"

},

{

"dataTypeName": "com.google.distance.delta",

"dataSourceId": "derived:com.google.distance.delta:com.google.android.gms:merge_distance_delta"

},

Diedrich S

Hi Robert and others interested,

I figured out what the problem was. It was getting the data, but it wasn't being treated correctly. In the function getMetricsForDays, there's a for loop where it looks like there's commands to assign numerical data types. In the case of move minutes, it's an integer value, and I was assigning it a floating point value, fpValue, and it should be intVal, just like the number of steps. After changing this, it worked just fine.

Thanks!,

Diedrich

Diedrich S

Hi Robert,

Thanks for your reply. I'm not sure how to check the JSON response, I am not that familiar with Java scripting.

The strange thing is that I get nothing for move minutes. I also don't get any error, either. But, what has changed is that I need to re-authorize the script each day, whereas before adding in the move minutes changes, I didn't need to.

The stackoverflow link I provided in the previous comment claims that it works for that person.

Since I'm not familiar with this type of scripting, I am not sure where to begin debugging it.

Thanks!,

Diedrich

Robert Ellison

Hi Diedrich, I'd start by examining the JSON response. Are you getting anything at all for move minutes? Maybe it's some small difference in how it needs to be parsed.

Diedrich S

Hi Robert,

Thank you so much for putting this together! I do not program in Java, and have been wanting to extract some of my data from Google Fit that doesn't necessarily come with the data when doing a Google Takeout, so I really appreciate you putting together this post, it really was easy getting it up and running.

I was able to modify your script to also bring in the aggregated calories expended as well as the aggregated heart points. One data type I'd like to bring in, but it's not working is move minutes. I'd like to bring in the total move minutes from a day.

According to the API documentation (https://developers.google.com/fit/datatypes/aggregate), the data type and field of aggregated move minutes is the same as the instantaneous data type (https://developers.google.com/fit/datatypes/activity#move_minutes).

So long as I have the right dataTypeName and dataSourceId (I do, I found them here: https://stackoverflow.com/questions/41173213/getting-active-time-from-google-fit-rest-api), I should be able to bring in this data just like I did with calories and heart points. Unfortunately, something is wrong and it doesn't bring any move minute data into the google spreadsheet. I get all the other data, but not move minutes. The OAuth permission scope is the same for other activity data types: https://www.googleapis.com/auth/fitness.activity.read.

I've added in the following to the var request:

{

"dataTypeName": "com.google.calories.expended",

"dataSourceId": "derived:com.google.calories.expended:com.google.android.gms:merge_calories_expended"

},

{

"dataTypeName" : "com.google.heart_minutes",

"dataSourceId" : "derived:com.google.heart_minutes:com.google.android.gms:merge_heart_minutes"

},

{

"dataTypeName": "com.google.active_minutes",

"dataSourceId": "derived:com.google.active_minutes:com.google.android.gms:merge_active_minutes"

}

I've also made these changes in the for loop:

var steps = -1;

var weight = -1;

var distance = -1;

var calories = -1;

var heartmin = -1;

var movemin = -1;

if (json.bucket[b].dataset[0].point.length > 0) {

steps = json.bucket[b].dataset[0].point[0].value[0].intVal;

}

if (json.bucket[b].dataset[1].point.length > 0) {

weight = json.bucket[b].dataset[1].point[0].value[0].fpVal;

}

if (json.bucket[b].dataset[2].point.length > 0) {

distance = json.bucket[b].dataset[2].point[0].value[0].fpVal;

}

if (json.bucket[b].dataset[3].point.length > 0) {

calories = json.bucket[b].dataset[3].point[0].value[0].fpVal;

}

if (json.bucket[b].dataset[4].point.length > 0) {

heartmin = json.bucket[b].dataset[4].point[0].value[0].fpVal;

}

if (json.bucket[b].dataset[5].point.length > 0) {

movemin = json.bucket[b].dataset[5].point[0].value[0].fpVal;

}

sheet.appendRow([bucketDate,

steps == -1 ? ' ' : steps,

weight == -1 ? ' ' : weight,

distance == -1 ? ' ' : distance,

calories == -1 ? ' ' : calories,

heartmin == -1 ? ' ' : heartmin,

movemin == -1 ? ' ' : movemin ]);

}

As mentioned before, all the other data, steps, weight, distance, calories, and heart minutes are pulled into the spreadsheet, just the move minutes are not brought in. I'm not sure what my mistake is.

Thanks!,

Diedrich

John Langstaff

Wow. I just want to, like, click on “Export”, pick a common file format, and tell it a destination, to get my data….Oh! It’s not _my_ data! It’s data I _gave_ them! Dopey my reasonably expectious me be!

Guga Alves

I've added new lines on my Metrics and Historic tabs to get data for 2023, but it is not working. I've already reset settings and authorized it again, but I can't get new data there... not sure what else I should do.

Robert Ellison

Yubin - follow the instructions near the end of the post for resetting authorization.

Add Comment

All comments are moderated. Your email address is used to display a Gravatar and optionally for notification of new comments and to sign up for the newsletter.

I Thought He Came With You is Robert Ellison's blog.

Newsletter

Related

Average Server Response Time in Azure Metrics