Skip to content
Docs menu

Calendar events

Races and other dated events on the Garmin Connect calendar: list, read, create, update, delete. Events you create are always private.

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);

6 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
createCalendarEventlive-verified
deleteCalendarEventlive-verified
getCalendarEventlive-verified
getSharedCalendarEventlive-verified
listCalendarEventslive-verified
updateCalendarEventlive-verified

createCalendarEvent

garmin.createCalendarEvent(input: CalendarEventInput): Promise<CalendarEvent | null>
const result = await garmin.createCalendarEvent(input);

Returns

CalendarEvent:

FieldTypeAlways present
idnumberyes
eventNamestringyes
datestringyes
url`stringnull`
registrationUrl`stringnull`
courseId`numbernull`
completionTarget`CalendarEventTargetnull`
eventTimeLocal`{ startTimeHhMm: string; timeZoneId: string }null`
note`stringnull`
workoutId`numbernull`
location`stringnull`
eventType`stringnull`
eventPrivacy{ label: string; isShareable: boolean; isDiscoverable: boolean }no
shareableEventUuid`stringnull`
eventCustomization{no
customGoal`CalendarEventTargetnull`
isPrimaryEventbooleanno
isTrainingEventbooleanno
associatedWithActivityId`numbernull`
isGoalMet`booleannull`
trainingPlanId`numbernull`
racebooleanno

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.

POSTs /calendar-service/event. CalendarEventInput = {name, date, startTime?: "HH:MM", timeZoneId?, eventType?, race?, primary?, distance?: {value, unit: meter|kilometer|yard|mile}, goalTimeSeconds?, location?, url?, note?}; only name/date are required by Garmin ('addEvent.arg3.eventName' must not be blank otherwise). Garmin forces every user-created event PRIVATE and unshareable whatever is sent, sets isTrainingEvent whenever isPrimaryEvent is, and answers an unknown eventType with a 500. It also rewrote Europe/Zagreb to Europe/Paris (same offset) while keeping America/New_York as sent

Verification: live-verified

deleteCalendarEvent

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

Returns

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

DELETE /calendar-service/event/{id}, resolves null (204). IRREVERSIBLE

Verification: live-verified

getCalendarEvent

garmin.getCalendarEvent(eventId: number | string): Promise<CalendarEvent | null>
const result = await garmin.getCalendarEvent(activityId);

Returns

CalendarEvent:

FieldTypeAlways present
idnumberyes
eventNamestringyes
datestringyes
url`stringnull`
registrationUrl`stringnull`
courseId`numbernull`
completionTarget`CalendarEventTargetnull`
eventTimeLocal`{ startTimeHhMm: string; timeZoneId: string }null`
note`stringnull`
workoutId`numbernull`
location`stringnull`
eventType`stringnull`
eventPrivacy{ label: string; isShareable: boolean; isDiscoverable: boolean }no
shareableEventUuid`stringnull`
eventCustomization{no
customGoal`CalendarEventTargetnull`
isPrimaryEventbooleanno
isTrainingEventbooleanno
associatedWithActivityId`numbernull`
isGoalMet`booleannull`
trainingPlanId`numbernull`
racebooleanno

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 /calendar-service/event/{id}; a missing id is a 404 GarminHttpError

Verification: live-verified

getSharedCalendarEvent

garmin.getSharedCalendarEvent(shareableEventUuid: string): Promise<CalendarEvent | null>
const result = await garmin.getSharedCalendarEvent(activityId);

Returns

CalendarEvent:

FieldTypeAlways present
idnumberyes
eventNamestringyes
datestringyes
url`stringnull`
registrationUrl`stringnull`
courseId`numbernull`
completionTarget`CalendarEventTargetnull`
eventTimeLocal`{ startTimeHhMm: string; timeZoneId: string }null`
note`stringnull`
workoutId`numbernull`
location`stringnull`
eventType`stringnull`
eventPrivacy{ label: string; isShareable: boolean; isDiscoverable: boolean }no
shareableEventUuid`stringnull`
eventCustomization{no
customGoal`CalendarEventTargetnull`
isPrimaryEventbooleanno
isTrainingEventbooleanno
associatedWithActivityId`numbernull`
isGoalMet`booleannull`
trainingPlanId`numbernull`
racebooleanno

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 /calendar-service/event/{uuid}/shareable: an event from Garmin's public events catalogue (Races & Events -> Find an Event), with locationStartPoint, subscribersCount, provider and subscribed. Works WITHOUT subscribing (subscribed: false). The plain /event/{uuid} is a 404. Events you create never get a shareable uuid (Garmin forces them private)

Verification: live-verified

listCalendarEvents

garmin.listCalendarEvents(startdate?: string | Date, enddate?: string | Date): Promise<CalendarEvent[] | null>
const result = await garmin.listCalendarEvents();

Returns

An array of CalendarEvent:

FieldTypeAlways present
idnumberyes
eventNamestringyes
datestringyes
url`stringnull`
registrationUrl`stringnull`
courseId`numbernull`
completionTarget`CalendarEventTargetnull`
eventTimeLocal`{ startTimeHhMm: string; timeZoneId: string }null`
note`stringnull`
workoutId`numbernull`
location`stringnull`
eventType`stringnull`
eventPrivacy{ label: string; isShareable: boolean; isDiscoverable: boolean }no
shareableEventUuid`stringnull`
eventCustomization{no
customGoal`CalendarEventTargetnull`
isPrimaryEventbooleanno
isTrainingEventbooleanno
associatedWithActivityId`numbernull`
isGoalMet`booleannull`
trainingPlanId`numbernull`
racebooleanno

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.

Events also appear in getScheduledWorkouts' month feed as itemType event; this GETs /calendar-service/events, with startDate/endDate when both dates are given (inclusive) or no params for every event on the account; one date alone throws

Verification: live-verified

updateCalendarEvent

garmin.updateCalendarEvent(eventId: number | string, changes: CalendarEventUpdate): Promise<unknown>
const result = await garmin.updateCalendarEvent(activityId, changes);

Returns

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

READ-MODIFY-WRITE: GETs the event, overlays {name?, date?, location?, url?, note?, race?, goalTimeSeconds?} (null clears an optional one), PUTs the whole record back to /calendar-service/event/{id}; Garmin answers with no body, so it resolves to null. Concurrent callers can clobber each other

Verification: live-verified

Methods in this category