Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with Google Fit

Integrating Google Fit with Lovable uses Edge Functions to handle Google's OAuth2 flow and proxy REST API requests for fitness data sources, datasets, and sessions. Store Google OAuth credentials in Cloud Secrets, implement the authorization code flow with fitness scope, and fetch step counts, heart rate, nutrition, and workout sessions. Setup takes 45 minutes and is ideal for Android-focused health apps.

What you'll learn

  • How to set up Google Fit API access in Google Cloud Console and configure OAuth2 credentials
  • How to implement Google's OAuth2 authorization code flow for Fitness API access
  • How to understand Google Fit's Fitness Store data model (data sources, types, datasets)
  • How to fetch step counts, heart rate, calories, and workout sessions via Edge Functions
  • How to build a cross-device fitness dashboard aggregating Android health data
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read45 minutesHealthMarch 2026RapidDev Engineering Team
TL;DR

Integrating Google Fit with Lovable uses Edge Functions to handle Google's OAuth2 flow and proxy REST API requests for fitness data sources, datasets, and sessions. Store Google OAuth credentials in Cloud Secrets, implement the authorization code flow with fitness scope, and fetch step counts, heart rate, nutrition, and workout sessions. Setup takes 45 minutes and is ideal for Android-focused health apps.

Why integrate Google Fit with Lovable?

Google Fit is the health data platform for Android — it aggregates fitness data from Wear OS watches, Android phone sensors, and third-party Android fitness apps (including Fitbit, Strava, Samsung Health, and many others) into a single unified repository. If your users are on Android and use any combination of fitness devices and apps, Google Fit provides the single integration point that covers all of them.

This aggregation role is Google Fit's primary advantage over device-specific APIs. Instead of integrating separately with Fitbit, Samsung Health, and Strava to cover your Android user base, a single Google Fit integration can access data flowing from all three providers (when the user has connected them to Google Fit). For fitness dashboards, wellness apps, and coaching tools targeting Android users, this breadth makes Google Fit the most practical integration choice.

Google Fit's data model is unique among fitness APIs: data is organized in a 'Fitness Store' with data sources (where data comes from), data types (what kind of data: steps, heart rate, etc.), and datasets (time-bucketed collections of data points). This model is more flexible than Fitbit's endpoint-per-metric approach but requires understanding the data source naming conventions and data type schemas before writing queries. This tutorial covers the most-used data types and provides query patterns for common fitness dashboard needs.

Integration method

Edge Function Integration

Google Fit has no native Lovable connector. Integration requires Supabase Edge Functions to manage Google's OAuth2 authorization code flow and proxy Google Fit REST API requests for data sources, datasets, and fitness sessions. Store Google OAuth credentials from Google Cloud Console in Cloud Secrets. All Fit API calls run through Edge Functions to keep credentials server-side. The Fitness Store data model requires understanding data source IDs and data type names specific to Google Fit.

Prerequisites

  • A Lovable project with Cloud enabled
  • A Google Cloud Console project with the Fitness API enabled
  • OAuth2 credentials (Client ID and Client Secret) configured in Google Cloud Console
  • An Android device or Google Fit account with some fitness data for testing
  • Basic understanding of Google's OAuth2 flow and Google Cloud Console navigation

Step-by-step guide

1

Set up Google Fit API in Google Cloud Console

Go to console.cloud.google.com and create a new project (or use an existing one). In the left sidebar, navigate to 'APIs & Services' → 'Library'. Search for 'Fitness API' and click 'Enable'. This activates the Google Fit REST API for your project. Next, configure the OAuth2 consent screen: go to 'APIs & Services' → 'OAuth consent screen'. If building for personal or testing use, select 'External'. Fill in the app name, user support email, and developer contact email. Under 'Scopes', add the fitness scopes you need: fitness.activity.read, fitness.body.read, fitness.heart_rate.read, fitness.nutrition.read, fitness.sleep.read. Add your app's domain to authorized domains. Then create OAuth2 credentials: go to 'APIs & Services' → 'Credentials' → 'Create Credentials' → 'OAuth Client ID'. Select 'Web application'. Add your app's callback URL to 'Authorized redirect URIs': https://your-app.lovable.app/auth/google-fit/callback. Click 'Create' and note down the Client ID and Client Secret shown. Google's OAuth2 authorization URL is https://accounts.google.com/o/oauth2/v2/auth. The token endpoint is https://oauth2.googleapis.com/token. The Fitness API base URL is https://www.googleapis.com/fitness/v1.

Pro tip: If your app is in 'Testing' mode in the OAuth consent screen, only explicitly added test users can authorize it. For a production app serving real users, you'll need to go through Google's OAuth verification process. For development and personal use, testing mode works fine.

Expected result: The Google Fitness API is enabled in your Google Cloud project. OAuth2 credentials with Client ID and Client Secret are created. The callback URL is configured.

2

Store credentials and implement Google OAuth2 flow

Open your Lovable project, click '+', select 'Cloud', and expand Secrets. Add GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, and GOOGLE_FITNESS_BASE ('https://www.googleapis.com/fitness/v1'). Google's OAuth2 flow is standard: construct an authorization URL including the fitness scopes, redirect the user, handle the callback with the authorization code, and exchange it for tokens using the token endpoint. Google uses JSON request bodies for token exchange (unlike form-encoded bodies used by some providers). Google access tokens expire after 1 hour. Google refresh tokens don't expire unless the user revokes access or the app hasn't been used for 6 months. Proactively refresh when within 5 minutes of expiry. An important Google-specific parameter: include access_type=offline in the authorization request to receive a refresh token. Without this, you get only an access token with no way to refresh when it expires. Also include prompt=consent on the first authorization to ensure Google returns a refresh token even if the user previously authorized your app.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/google-fit-oauth/index.ts. Handle: action='get-auth-url' with user_id — return Google OAuth2 authorization URL with fitness scopes (fitness.activity.read, fitness.body.read, fitness.heart_rate.read), access_type=offline, prompt=consent, state; action='exchange' with code and user_id — POST to https://oauth2.googleapis.com/token with grant_type=authorization_code; store access_token, refresh_token, expires_at in google_fit_tokens table; action='get-token' — return valid access_token (auto-refresh if expiring).

Paste this in Lovable chat

supabase/functions/google-fit-oauth/index.ts
1// supabase/functions/google-fit-oauth/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
4
5const CLIENT_ID = Deno.env.get("GOOGLE_CLIENT_ID") ?? "";
6const CLIENT_SECRET = Deno.env.get("GOOGLE_CLIENT_SECRET") ?? "";
7const REDIRECT_URI = Deno.env.get("GOOGLE_REDIRECT_URI") ?? "";
8const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" };
9
10serve(async (req) => {
11 if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
12 const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "");
13 const body = await req.json();
14
15 try {
16 if (body.action === "get-auth-url") {
17 const { user_id } = body;
18 const state = crypto.randomUUID();
19 await supabase.from("oauth_states").insert({ user_id, state, provider: "google_fit", created_at: new Date().toISOString() });
20 const params = new URLSearchParams({
21 client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, response_type: "code",
22 scope: "https://www.googleapis.com/auth/fitness.activity.read https://www.googleapis.com/auth/fitness.body.read https://www.googleapis.com/auth/fitness.heart_rate.read",
23 access_type: "offline", prompt: "consent", state,
24 });
25 return new Response(JSON.stringify({ url: `https://accounts.google.com/o/oauth2/v2/auth?${params}` }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
26 }
27
28 if (body.action === "exchange") {
29 const { code, user_id } = body;
30 const res = await fetch("https://oauth2.googleapis.com/token", {
31 method: "POST",
32 headers: { "Content-Type": "application/json" },
33 body: JSON.stringify({ code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, grant_type: "authorization_code" }),
34 });
35 const data = await res.json();
36 if (!data.access_token) throw new Error(data.error_description ?? "Token exchange failed");
37 const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
38 await supabase.from("google_fit_tokens").upsert({ user_id, access_token: data.access_token, refresh_token: data.refresh_token, expires_at: expiresAt }, { onConflict: "user_id" });
39 return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
40 }
41
42 if (body.action === "get-token") {
43 const { user_id } = body;
44 const { data: token } = await supabase.from("google_fit_tokens").select("*").eq("user_id", user_id).single();
45 if (!token) throw new Error("No Google Fit connection found.");
46 if (new Date(token.expires_at) < new Date(Date.now() + 300_000)) {
47 const res = await fetch("https://oauth2.googleapis.com/token", {
48 method: "POST",
49 headers: { "Content-Type": "application/json" },
50 body: JSON.stringify({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, refresh_token: token.refresh_token, grant_type: "refresh_token" }),
51 });
52 const data = await res.json();
53 const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
54 await supabase.from("google_fit_tokens").update({ access_token: data.access_token, expires_at: expiresAt }).eq("user_id", user_id);
55 return new Response(JSON.stringify({ access_token: data.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
56 }
57 return new Response(JSON.stringify({ access_token: token.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
58 }
59
60 return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });
61 } catch (err) {
62 return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
63 }
64});

Pro tip: Google only returns a refresh_token on the first authorization. If you need to re-get a refresh token (e.g., after accidentally deleting it), the user must revoke access and re-authorize. Include prompt=consent in your authorization URL to force Google to always show the consent screen and always return a refresh_token.

Expected result: The google-fit-oauth Edge Function handles auth URL generation, token exchange, and refresh. After Google OAuth consent, tokens are stored in Supabase.

3

Create the Google Fit data Edge Function

Google Fit's REST API uses a 'Fitness Store' model. The most useful endpoint for dashboard data is POST /users/me/dataset:aggregate, which aggregates data from all sources for a specified time range and data type. This is simpler than querying individual data sources. For the aggregate endpoint, the request body specifies: aggregateBy (array of data type names), bucketByTime (time period for grouping, e.g., 86400000ms = 1 day), startTimeMillis, and endTimeMillis. Response returns buckets with each bucket containing data points for the specified time period. Key Google Fit data type names: com.google.step_count.delta for steps, com.google.calories.expended for calories, com.google.heart_rate.bpm for heart rate, com.google.active_minutes for active minutes, com.google.weight for body weight. Each data type has specific field names in its data point structure. For workout sessions, use GET /users/me/sessions with startTime and endTime parameters. Sessions include activity type (using Google's ActivityType constants: 1=biking, 7=running, 17=walking, 95=yoga, etc.), start and end times, and a name if set by the recording app.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/google-fit-data/index.ts. Get user access_token from google-fit-oauth. Support: action='activity-summary' with start_ms and end_ms — POST to /users/me/dataset:aggregate with daily buckets for step_count, calories, active_minutes; return array of {date, steps, calories, active_minutes}; action='sessions' with start_ms and end_ms — GET /users/me/sessions; return array of {session_id, name, activity_type, start_time, end_time, duration_minutes}; action='heart-rate' with start_ms and end_ms — aggregate heart_rate.bpm; return daily averages.

Paste this in Lovable chat

Pro tip: Google Fit timestamps are in nanoseconds in some contexts and milliseconds in others. The aggregate endpoint uses milliseconds (startTimeMillis, endTimeMillis). The sessions endpoint uses RFC 3339 format strings. Always check the API documentation for the expected timestamp format for each specific endpoint.

Expected result: The google-fit-data Edge Function returns daily activity summaries and workout sessions from the connected Google Fit account.

Common use cases

Cross-device Android fitness dashboard

A fitness tracking app aggregates step counts, active minutes, and calorie data from all of a user's connected Android devices and fitness apps via Google Fit. An Edge Function queries the Fitness Store's aggregated dataset for the past 30 days, bucketing data by day. The dashboard shows daily activity bars, weekly totals, and goal achievement tracking without requiring separate integrations for each device.

Lovable Prompt

Build an Android fitness dashboard using Google Fit. Create an Edge Function that fetches aggregated step count, calories, and active minutes from the Google Fit REST API for the past 30 days using the users.dataset.aggregate endpoint with daily buckets. Return an array of {date, steps, calories, active_minutes}. Display: a bar chart of daily steps with a 10,000 step goal line, weekly average stats cards, and a calendar heatmap showing activity levels by day.

Copy this prompt to try it in Lovable

Workout session tracker with performance metrics

A training log app fetches all workout sessions recorded in Google Fit from any connected app. Each session includes session type, duration, and associated performance metrics (heart rate, distance, calories). The app displays a history of workouts with filtering by activity type and trend charts for key metrics over time.

Lovable Prompt

Build a workout history page. Fetch fitness sessions from Google Fit's /sessions endpoint for the past 90 days. For each session, also fetch associated heart rate and distance datasets. Return sessions with: activity_type, start_time, duration_minutes, avg_heart_rate, max_heart_rate, distance_km, calories. Display in a list sorted by date with activity type icons, key metrics, and filtering by activity type (running, cycling, yoga, etc.).

Copy this prompt to try it in Lovable

Daily step and heart rate correlation analysis

A health insights app analyzes the relationship between daily step counts and resting heart rate trends using Google Fit data. The Edge Function fetches 60 days of both metrics and the frontend displays a scatter plot showing how activity levels correlate with resting heart rate, with trend lines calculated client-side.

Lovable Prompt

Build a health correlations page. Fetch 60 days of daily step counts and daily average resting heart rate from Google Fit via Edge Function. Return an array of {date, steps, resting_hr}. Display: a dual-axis line chart showing both metrics over time, a scatter plot of steps vs resting HR with a trend line, correlation coefficient displayed as a health insight card, and highlighted weeks where both metrics changed significantly.

Copy this prompt to try it in Lovable

Troubleshooting

OAuth returns 'access_denied' error during authorization

Cause: Your Google Cloud app is in 'Testing' mode and the authorizing user is not on the allowed test users list, or the OAuth consent screen has not been configured with the required scopes.

Solution: In Google Cloud Console, go to APIs & Services → OAuth consent screen → Test users. Add the Google email address of the person trying to authorize. Alternatively, publish the app (which requires Google review for sensitive scopes). Also verify the fitness scopes are listed in the OAuth consent screen's scopes section.

Fitness API returns empty datasets even though Google Fit shows data

Cause: The time range in your query may be in the wrong format (seconds vs. milliseconds), the data type name may be misspelled, or the user's fitness data is stored in a data source that isn't being captured by the aggregate query.

Solution: Verify startTimeMillis and endTimeMillis are Unix timestamps in milliseconds (13-digit numbers for current dates). Confirm the data type name exactly matches Google's specification: 'com.google.step_count.delta', 'com.google.calories.expended'. Test with a simple query for yesterday's steps before building complex multi-metric aggregations.

typescript
1// Ensure timestamps are milliseconds
2const start_ms = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days ago
3const end_ms = Date.now();
4// Both should be 13-digit numbers

Token refresh fails with 'invalid_grant' error

Cause: Google invalidates refresh tokens when a user changes their Google password, revokes app access, or when the refresh token has been inactive for 6 months. 'invalid_grant' means the refresh token is no longer valid.

Solution: When receiving 'invalid_grant' during token refresh, delete the user's stored tokens and prompt them to re-authorize your app through the Google OAuth flow. Show a clear message: 'Your Google Fit connection has expired. Please reconnect.' The user will need to go through the full OAuth consent flow again.

typescript
1if (data.error === 'invalid_grant') {
2 await supabase.from('google_fit_tokens').delete().eq('user_id', user_id);
3 throw new Error('Google Fit authorization expired. Please reconnect your account.');
4}

Best practices

  • Always include access_type=offline and prompt=consent in the Google OAuth2 authorization URL to ensure you receive a refresh token — without it, tokens expire in 1 hour with no way to renew.
  • Request only the Google Fit scopes your app actually uses — each additional scope is shown to users during consent and increases friction. Start with fitness.activity.read and add others as features require them.
  • Use the /dataset:aggregate endpoint rather than querying individual data sources — aggregation handles data from multiple devices and apps automatically, giving a complete picture across the user's Android ecosystem.
  • Store daily activity aggregations in Supabase after fetching — don't re-query Google Fit for historical data that won't change. Fetch today's data fresh but serve historical data from your database.
  • Handle the case where a user has no Google Fit data — many Android users have Google Fit installed but no connected apps or devices, resulting in empty datasets. Show onboarding guidance for setting up Fit tracking.
  • Google Fit timestamps in responses use nanoseconds in some endpoints and milliseconds in others — always check and convert appropriately: nanoseconds = Date.now() * 1_000_000 or divide by 1_000_000 when reading.
  • Implement a data backfill on first connection to populate historical charts — request 90 days of historical data when a user first authorizes so the dashboard shows meaningful trends immediately.

Alternatives

Frequently asked questions

Does Google Fit work on iOS devices?

Google Fit has an iOS app, but its data integration on iOS is limited compared to Android. On iOS, Google Fit reads data from HealthKit but doesn't have the deep Android ecosystem integrations. For iOS users, Apple HealthKit is the better integration choice. Google Fit is most valuable for Android users where it aggregates data from Wear OS watches, Android phone sensors, and Android-native fitness apps.

What happens to Google Fit data when users uninstall a connected fitness app?

Data already stored in the Google Fit Fitness Store persists even after the contributing app is uninstalled. The data source records remain, though new data from that source stops flowing. When you query historical data via the aggregate endpoint, the historical data is still returned. This means a complete activity history is available even if the user has changed fitness apps multiple times over the years.

Can I write fitness data to Google Fit from my Lovable app?

Yes — Google Fit's REST API supports both read and write operations. To write data, request the write variants of the scopes: fitness.activity.write, fitness.body.write. You'd create a new data source for your app, then insert data points or sessions using POST /users/me/dataSources and POST /users/me/dataSources/{dataSourceId}/datasets. This enables recording workouts or manual fitness entries that appear in the user's Google Fit app.

Is Google Fit being discontinued?

As of March 2026, Google Fit remains active but has received limited new feature development. Google has been consolidating health features into Android's Health Connect platform, which provides a more privacy-focused, on-device health data store. Health Connect is the successor to Google Fit for Android 14+ devices. If you're building for the latest Android versions, consider investigating Health Connect's REST API as the future-proof option, while maintaining Google Fit for backward compatibility.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.