Skip to content
Docs menu

Profile, goals, nutrition, plans & misc

User profile and settings, goals, nutrition logs, training plans, and the odds and ends — lifestyle logging, a data-reload request, the GraphQL passthrough, and logout.

Every method below hangs off a Garmin instance. See Installation & setup for how to construct one:

import { GarminClient, Garmin, FileTokenStore } from "garminconnect-js";

const client = new GarminClient({ tokenStore: new FileTokenStore("./tokens") });
if (!(await client.loadTokens())) throw new Error("not connected to Garmin");
const garmin = new Garmin(client);

30 methods. The verification column says what has been confirmed against a live Garmin account, not merely unit-tested — AGENTS.md carries the full evidence per method.

MethodVerified
createCustomFoodlive-verified · Connect+
deleteCustomFoodlive-verified · Connect+
deleteFoodLogslive-verified · Connect+
deleteTrainingPlanlive-verified
displayNamelive-verified (indirectly)
fullNamelive-verified (indirectly)
getAdaptiveTrainingPlanByIdlive-verified
getCustomFoodslive-verified · Connect+
getCustomFoodServingUnitslive-verified · Connect+
getGoalslive-verified
getLifestyleLoggingDatalive-verified
getNutritionDailyFoodLoglive-verified
getNutritionDailyMealslive-verified
getNutritionDailySettingslive-verified
getNutritionFoodLogRangelive-verified
getTrainingPlanByIdlive-verified
getTrainingPlanslive-verified
getUserProfilelive-verified
getUserprofileSettingslive-verified
getUserSettingslive-verified
hasConnectPluslive-verified
logFoodlive-verified · Connect+
logout— not applicable
queryGarminGraphqllive-verified
quickAddFoodlive-verified · Connect+
requestReloadlive-verified
searchFoodslive-verified · Connect+
unitSystemlive-verified
updateCustomFoodlive-verified · Connect+
userNamelive-verified (indirectly)

createCustomFood

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.createCustomFood(input: CustomFoodInput): Promise<Food | null>
const result = await garmin.createCustomFood(input);

Returns

Food:

FieldTypeAlways present
foodMetaData{yes
foodIdstringyes
foodNamestringyes
brandNamestringno
sourcestringyes
regionCodestringyes
languageCodestringyes
nutritionContentsFoodServing[]yes
foodImagesunknown[]no
isFavoritebooleanno

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

A PUT, not a POST, to /nutrition-service/customFood; CustomFoodInput = {name, calories, servingUnit? = "G", servingSize? = 100, brand?, carbs?, protein?, fat?, fiber?, sugar?, saturatedFat?, transFat?, sodium?, cholesterol?, potassium?, calcium?, iron?, vitaminD?} per ONE serving, absolute amounts (not %DV); numbers are sent as strings, as Garmin's own client does. Returns the stored food with the foodId/servingId logFood takes. Needs Connect+

Verification: live-verified

deleteCustomFood

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.deleteCustomFood(foodId: string): Promise<unknown>
const result = await garmin.deleteCustomFood(activityId);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

DELETE /nutrition-service/customFood/{foodId}, resolves null. IRREVERSIBLE. Needs Connect+

Verification: live-verified

deleteFoodLogs

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.deleteFoodLogs(date: string | Date, logIds: string[]): Promise<unknown>
const result = await garmin.deleteFoodLogs("2026-09-24", ["running"]);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

DELETE /nutrition-service/food/logs/{date} with {logIds} as the body — any number in ONE call, regular and quick-add alike. logIds are on the entries of getNutritionDailyFoodLog. IRREVERSIBLE. Needs Connect+

Verification: live-verified

deleteTrainingPlan

garmin.deleteTrainingPlan(planId: number | string): Promise<unknown>
const result = await garmin.deleteTrainingPlan(activityId);

Returns

unknown — Garmin's response is passed through unparsed. Cast it to whatever you need; this library does not model it.

DELETE /trainingplan-service/trainingplan/trainingplan/{planId} (the trainingplan segment really is doubled) — the request Garmin Connect's "Quit Plan" sends, observed in the web client on 2026-10-06. Resolves null (204); the plan's scheduled workouts leave the calendar, completed activities stay. A second call is a 404 "Training plan not found with ID: …". IRREVERSIBLE

Verification: live-verified

displayName

garmin.displayName(): Promise<string>
const result = await garmin.displayName();

Returns

string

Verification: live-verified (indirectly)

fullName

garmin.fullName(): Promise<string>
const result = await garmin.fullName();

Returns

string

Verification: live-verified (indirectly)

getAdaptiveTrainingPlanById

garmin.getAdaptiveTrainingPlanById(planId: number | string): Promise<AdaptiveTrainingPlanDetail | null>
const result = await garmin.getAdaptiveTrainingPlanById(activityId);

Returns

AdaptiveTrainingPlanDetail — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /trainingplan-service/trainingplan/fbt-adaptive/{planId}, a distinct sub-path from getTrainingPlanById's phased path; passes through unchecked

Verification: live-verified

getCustomFoods

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.getCustomFoods(search?: string, start?: number, limit?: number): Promise<CustomFoodList | null>
const result = await garmin.getCustomFoods();

Returns

CustomFoodList:

FieldTypeAlways present
customFoodsFood[]yes
moreDataAvailablebooleanyes

GETs /nutrition-service/customFood with includeContent=true: {customFoods, moreDataAvailable}. limit is capped at 20 (Garmin 400s above it). There is NO get-by-id: GET /customFood/{id} is a 405, so search by name to read one back. Needs Connect+

Verification: live-verified

getCustomFoodServingUnits

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.getCustomFoodServingUnits(): Promise<{servingUnits: {name}[]} | null>
const result = await garmin.getCustomFoodServingUnits();

Returns

{servingUnits: {name}[]}

GETs /nutrition-service/metadata/customFoodServingUnits (13 units). Needs Connect+

Verification: live-verified

getGoals

garmin.getGoals(status?: "active" | "future" | "past", start?: number, limit?: number): Promise<Goal[]>
const result = await garmin.getGoals();

Returns

An array of Goal — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

defaults status="active", **start=1**, limit=30 — start defaults to 1, NOT 0, because goal-service is 1-INDEXED and start=0 silently returns []; throws GarminError before any request for an invalid status. Paginated, multi-call: starting at start, fetches successive pages of limit entries (incrementing start by limit each call) until a page comes back empty/falsy, same fixed-page-size pattern as getActivitiesByDate; throws GarminError if MAX_PAGINATED_REQUESTS (2000) pages are fetched without ever seeing an empty one. Sends the load-bearing Sec-Fetch-Site: same-origin header on every request — without it goal-service silently returns [] for newer custom accumulation-goal types; no error, no 404, just wrong data

Verification: live-verified

getLifestyleLoggingData

garmin.getLifestyleLoggingData(cdate: string | Date): Promise<LifestyleLoggingData | null>
const result = await garmin.getLifestyleLoggingData("2026-09-24");

Returns

LifestyleLoggingData — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /lifestylelogging-service/dailyLog/{cdate}; passes through unchecked. Grouped under misc per the plan's explicit instruction, even though it superficially resembles a wellness-daily endpoint

Verification: live-verified

getNutritionDailyFoodLog

garmin.getNutritionDailyFoodLog(cdate: string | Date): Promise<NutritionDailyFoodLog | null>
const result = await garmin.getNutritionDailyFoodLog("2026-09-24");

Returns

NutritionDailyFoodLog — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /nutrition-service/food/logs/{cdate}; passes through unchecked

Verification: live-verified

getNutritionDailyMeals

garmin.getNutritionDailyMeals(cdate: string | Date): Promise<NutritionDailyMeals | null>
const result = await garmin.getNutritionDailyMeals("2026-09-24");

Returns

NutritionDailyMeals — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /nutrition-service/meals/{cdate}; passes through unchecked

Verification: live-verified

getNutritionDailySettings

garmin.getNutritionDailySettings(cdate: string | Date): Promise<NutritionDailySettings | null>
const result = await garmin.getNutritionDailySettings("2026-09-24");

Returns

NutritionDailySettings — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /nutrition-service/settings/{cdate}; passes through unchecked

Verification: live-verified

getNutritionFoodLogRange

garmin.getNutritionFoodLogRange(startdate: string | Date, enddate: string | Date): Promise<NutritionFoodLogRange | null>
const result = await garmin.getNutritionFoodLogRange("2026-09-24", "2026-09-24");

Returns

NutritionFoodLogRange:

FieldTypeAlways present
dailyNutritionSummariesNutritionDailyFoodLog[]yes

GETs /nutrition-service/food/logs/range?startDate&endDate: {dailyNutritionSummaries}, one day-log per day that has anything logged. The ONLY food-logging call that works without Connect+ (returns no days then)

Verification: live-verified

getTrainingPlanById

garmin.getTrainingPlanById(planId: number | string): Promise<TrainingPlanDetail | null>
const result = await garmin.getTrainingPlanById(activityId);

Returns

TrainingPlanDetail — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /trainingplan-service/trainingplan/phased/{planId}; passes through unchecked

Verification: live-verified

getTrainingPlans

garmin.getTrainingPlans(): Promise<TrainingPlansResult | null>
const result = await garmin.getTrainingPlans();

Returns

TrainingPlansResult:

FieldTypeAlways present
trainingPlanListunknown[]no

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

GETs /trainingplan-service/trainingplan/plans; no params; passes through unchecked

Verification: live-verified

getUserProfile

garmin.getUserProfile(): Promise<SocialProfile>
const result = await garmin.getUserProfile();

Returns

SocialProfile:

FieldTypeAlways present
displayNamestringyes
userNamestringyes
fullNamestringyes
profileIdnumberyes

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

Verification: live-verified

getUserprofileSettings

garmin.getUserprofileSettings(): Promise<UserprofileSettings | null>
const result = await garmin.getUserprofileSettings();

Returns

UserprofileSettings — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

GETs /userprofile-service/userprofile/settings (SINGULAR "settings", distinct from getUserSettings's "user-settings" — the two paths are one character apart and easy to transpose); passes through unchecked. Three profile endpoints, three methods: getUserProfile() reads /userprofile-service/socialProfile (and backs displayName/fullName/userName), getUserSettings() reads /userprofile-service/userprofile/user-settings (and backs unitSystem), and this one reads .../settings

Verification: live-verified

getUserSettings

garmin.getUserSettings(): Promise<UserSettings>
const result = await garmin.getUserSettings();

Returns

UserSettings:

FieldTypeAlways present
idnumberno
userData{no
measurementSystemstringno

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

Verification: live-verified

hasConnectPlus

garmin.hasConnectPlus(): Promise<boolean>
const result = await garmin.hasConnectPlus();

Returns

boolean

whether the account has a Garmin Connect+ subscription, read from the cached user profile (/userprofile-service/socialProfile, so usually no extra request): true when hasPremiumSocialIcon is true or userRoles holds any ROLE_SP_FEATURE_n entry. The methods in CONNECT_PLUS_METHODS (searchFoods, getCustomFoods, getCustomFoodServingUnits, createCustomFood, updateCustomFood, deleteCustomFood, logFood, quickAddFood, deleteFoodLogs) need it; without it they throw GarminConnectPlusRequiredError instead of the bare 403 Garmin sends

Verification: live-verified

logFood

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.logFood(input: FoodLogInput): Promise<NutritionDailyFoodLog | null>
const result = await garmin.logFood(input);

Returns

NutritionDailyFoodLog — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

PUTs /nutrition-service/food/logs with one REGULAR_LOG item and returns the whole day's log. FoodLogInput = {date, foodId, servingId, servings? = 1, time?, meal?, source? = "GARMIN", regionCode?, languageCode?} — pass the catalogue food's source/regionCode/languageCode for a search result. Needs a mealId, which only exists after Garmin's nutrition setup in the app (400 mealId must not be null otherwise; this method throws a clearer error first). The meal is the named one, else the one whose window holds time, else SNACKS; with a meal and no time it picks a time that fits. Garmin VALIDATES the pairing: a snack inside LUNCH's window is a 400 "Meal time for Snacks overlap with meal type: LUNCH". Needs Connect+

Verification: live-verified

logout

garmin.logout(): Promise<void>
const result = await garmin.logout();

Returns

Nothing.

clears the configured TokenStore (host.client.tokenStore.clear()); makes no HTTP call (the token is never revoked server-side). Does NOT clear the in-memory tokens already held by the calling GarminClient instance — there is no public API to do that, and this method's host is deliberately scoped to { client } only. NEVER call this against a FileTokenStore pointed at ./tokens — that is the test harness's live session

Verification: — not applicable

queryGarminGraphql

garmin.queryGarminGraphql(query: Record<string, unknown>): Promise<GraphqlResult | null>
const result = await garmin.queryGarminGraphql("query");

Returns

GraphqlResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

POSTs the caller's GraphQL body verbatim to /graphql-gateway/graphql. The leading slash is load-bearing: connectapi composes the request URL by plain string concatenation (`https://connectapi.${domain}${path}`), not URL-relative joining, so omitting the leading slash here would silently glue onto the hostname (connectapi.garmin.comgraphql-gateway/graphql) rather than 404 — the usual "a 404 means the URL is wrong" heuristic would not even catch it. The leading slash is therefore hardcoded and deliberate; tests/services/misc.test.ts pins the literal composed URL

Verification: live-verified

quickAddFood

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.quickAddFood(input: QuickAddInput): Promise<NutritionDailyFoodLog | null>
const result = await garmin.quickAddFood(input);

Returns

NutritionDailyFoodLog — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

PUTs /nutrition-service/food/logs/quickAdd: an entry by name + calories/carbs/protein/fat with no food behind it (QUICK_ADD). Same meal rules as logFood. Needs Connect+

Verification: live-verified

requestReload

garmin.requestReload(cdate: string | Date): Promise<ReloadRequestResult | null>
const result = await garmin.requestReload("2026-09-24");

Returns

ReloadRequestResult — an object whose fields this library does not model. Garmin's response is passed through unparsed, so read one to see what you get, or use a Record<string, unknown> and narrow it yourself.

POSTs /wellness-service/wellness/epoch/request/{cdate} with no JSON body; asks Garmin to reload/recompute a day's data (Garmin offloads older data, so this forces it back)

Verification: live-verified

searchFoods

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.searchFoods(query: string, start?: number, limit?: number): Promise<FoodSearchResult | null>
const result = await garmin.searchFoods("query");

Returns

FoodSearchResult:

FieldTypeAlways present
resultsFood[]yes
moreDataAvailablebooleanyes

GETs /nutrition-service/food/search?searchExpression&start&limit (defaults 0/20): {results: Food[], moreDataAvailable}; catalogue foods carry source: "FATSECRET" and several servings each. Needs Garmin Connect+, which needs a paired Garmin device — a bare 403 ForbiddenException without it

Verification: live-verified

unitSystem

garmin.unitSystem(): Promise<string | undefined>
const result = await garmin.unitSystem();

Returns

string | undefined

Verification: live-verified

updateCustomFood

Requires Garmin Connect+. Without it this throws GarminConnectPlusRequiredError; check first with garmin.hasConnectPlus().

garmin.updateCustomFood(foodId: string, servingId: string, input: CustomFoodInput): Promise<Food | null>
const result = await garmin.updateCustomFood(activityId, activityId, input);

Returns

Food:

FieldTypeAlways present
foodMetaData{yes
foodIdstringyes
foodNamestringyes
brandNamestringno
sourcestringyes
regionCodestringyes
languageCodestringyes
nutritionContentsFoodServing[]yes
foodImagesunknown[]no
isFavoritebooleanno

Plus every other field Garmin sends: this type carries an index signature because the real response is wider than the fields above, which are the ones this library relies on or has observed. Read an actual response before depending on a field that is not listed.

The same PUT carrying both ids. FULL REPLACE: a nutrient or brand left out is removed (verified: the brand was dropped). Needs Connect+

Verification: live-verified

userName

garmin.userName(): Promise<string>
const result = await garmin.userName();

Returns

string

Verification: live-verified (indirectly)

Methods in this category