calcom/cal.diy · error · UnauthorizedException

Google Calendar is not connected for this user

Error message

Google Calendar is not connected for this user

What it means

Thrown by getCalendarClientForUser when findCredentialWithDelegationByTypeAndUserId returns null — no AppCredential row of type google_calendar exists for the given userId. Returns HTTP 401 via UnauthorizedException. This is the user-scoped entry point used by listEventsForUser/createEventForUser/deleteEventForUser.

Source

Thrown at apps/api/v2/src/modules/cal-unified-calendars/services/google-calendar.service.ts:177

      await authClient.authorize();
      return new calendar_v3.Calendar({ auth: authClient });
    } catch (error) {
      return null;
    }
  }

  /**
   * Gets an authorized Google Calendar instance for the given user (for user-scoped list/create/delete).
   * Tries delegated auth first (if available), then falls back to direct OAuth.
   */
  async getCalendarClientForUser(userId: number): Promise<calendar_v3.Calendar> {
    const credential = await this.credentialsRepository.findCredentialWithDelegationByTypeAndUserId(
      GOOGLE_CALENDAR_TYPE,
      userId
    );
    if (!credential) {
      throw new UnauthorizedException("Google Calendar is not connected for this user");
    }
    if (credential.invalid) {
      throw new UnauthorizedException("Google Calendar credentials are invalid. Please reconnect.");
    }
    return this.getAuthorizedCalendarInstance(
      credential.user?.email ?? undefined,
      credential.key,
      credential.delegationCredentialId ? { id: credential.delegationCredentialId } : null
    );
  }

  /**
   * Gets an authorized Google Calendar instance for a specific credential (connection).
   * Tries delegated auth first (if available), then falls back to direct OAuth.
   */
  async getCalendarClientByCredentialId(userId: number, credentialId: number): Promise<calendar_v3.Calendar> {
    const credential = await this.credentialsRepository.findCredentialByIdAndUserId(credentialId, userId);
    if (!credential) {

View on GitHub (pinned to 176037d0af)

Solutions

  1. Have the user complete the Google Calendar OAuth connect flow to create a google_calendar credential row.
  2. Verify the userId passed matches the credential owner (SELECT "userId", type FROM "AppCredential" WHERE type='google_calendar' AND "userId"=?).
  3. If the user should use a team/org-scoped calendar, call the connection-scoped endpoints with the correct credentialId instead.
  4. Surface a clear UI prompt to connect Google Calendar before allowing event operations.

Example fix

// before
const events = await googleCalendarService.listEventsForUser(userId, calId, t0, t1);

// after: validate connection state first
const cred = await credentialsRepository.findCredentialWithDelegationByTypeAndUserId(GOOGLE_CALENDAR_TYPE, userId);
if (!cred) {
  return { status: 'requires_connection', url: '/apps/google-calendar' };
}
Defensive patterns

Strategy: validation

Validate before calling

async function ensureGoogleCalendarConnected(userId: number): Promise<void> {
  const cred = await credentialsRepository.findCredentialWithDelegationByTypeAndUserId(GOOGLE_CALENDAR_TYPE, userId);
  if (!cred) {
    throw new Error(`User ${userId} has no google_calendar credential. Direct them to connect.`);
  }
}

await ensureGoogleCalendarConnected(userId);

Type guard

interface HasGoogleCalendar {
  type: string;
  userId: number;
}

function isGoogleCalendarCredential<T extends { type: string }>(c: T | null): c is T & { type: 'google_calendar' } {
  return c !== null && c.type === 'google_calendar';
}

Try / catch

try {
  await googleCalendarService.listEventsForUser(userId, calId, t0, t1);
} catch (e) {
  if (e instanceof UnauthorizedException && /not connected/.test(e.message)) {
    return res.status(422).json({ code: 'calendar_not_connected', connectUrl: '/apps/google-calendar' });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any user-scoped unified-calendar endpoint (POST /v2/calendars/{calendar}/events, GET .../events, DELETE) for a user who never connected Google Calendar, or whose only google_calendar credential belongs to a different userId (team/org credentials are not returned by this user-scoped query).

Common situations: User signed up but skipped calendar onboarding; the google_calendar credential was deleted via the disconnect flow; testing with a userId from a different environment; user connected only Office 365 or Apple.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/889094bf5e0e5891. Report an issue: GitHub.