How to Integrate Google Calendar API Into Your Appw

Authors
Published on

Integrate all calendars using a single API

Join our Unified Calendar API waitlist to get access to our Unified Calendar API, which allows you to integrate all calendar providers into your app using a single API.

Join WaitlistNo credit card required!

In this guide, we’ll thoroughly explain how to integrate the Google Calendar API into your app, covering Google Cloud project setup, required scopes, gotchas, and a real-world authorization example.

Prerequisites

This guide assumes that you have an email address, an existing domain ready to use when setting up the Google Cloud Project, some coding background, and a somewhat clear idea of what you’re trying to build.

This guide is also helpful if you’ve never worked with the Google Calendar API and want to get more familiar with all the steps required to integrate the Google Calendar API into your app.

How to use and integrate the Google Calendar API into your app

1. Sign up for the Google Developer Console

In case you don’t have a Google Developer Console account, you should create one at https://console.cloud.google.com/.

2. Create or choose from an existing Google Cloud Project

Google Cloud allows developers and organizations to have multiple projects. Please make sure you’re in the right project when doing the following operations.

Click the projects dropdown on the top left of the screen. The name usually matches the name of your project.

Google Cloud Home

Select an existing project, or create a new project by clicking "New Project" on the top right of the modal.

Google Cloud - Select Project

3. Enable the Google Calendar API services

After you’ve created an account and made sure you’re in the right project, follow these steps to enable the Google Calendar API services:

  1. Click “APIs & Services”
    Google Cloud - Click APIs and Services
  2. Click “Enable APIs and services”
    Google Cloud - Enable APIs and services
  3. Search for “Google Calendar API”
    Search for Google Calendar API service
  4. Click "Enable" to enable the service.
    Enable Google Calendar API service

After enabling the Google Calendar API services, the next step is to set up the OAuth Consent Screen. The OAuth Consent Screen is the interface that end users see when connecting their calendar to your app. Usually, they’re able to see your app logo, name, what permissions you require, and more.

  1. Click the "OAuth Consent Screen" tab.
    Click OAuth Consent Screen
  2. Click "Get Started".
    Click Get Started
  3. Fill in the “App Information” section. In this section, you should enter your app name and customer support email.
    Fill in the “App Information” section
  4. Choose the audience. Audience can be internal or external. Choose internal if your app won’t be public, and only users within your organization can connect their calendars through your app. Choose external if any public accounts will sign in, regardless if they’re part of your organization or not.
    Choose the audience
  5. Fill in your contact information. Google requires you to enter an email to notify you about any changes to your project.
    Fill in your contact information.webp
  6. Check the "Agree to the Google API Services: User Data Policy" checkbox.
    Check the Agree to the Google API Services
  7. Click “Create”.
    Click “Create”

5. Create your OAuth Client

After setting up the OAuth Consent Screen, you can create your OAuth client for the project. To do so, click the “Clients” tab, then click ‘Create Client”.

Alternatively, click "Create OAuth Client" on the overview page.

Create OAuth Client

You can create clients for each platform your app will run on. For example, if you are building a web app and an iOS app, you’ll need to create a separate OAuth Client ID for each platform.

Google Cloud - Client Options

In this example, we’ll just create a “Web Application” Client and call it “Web Client”.

Google Cloud - Web Client

In this flow, we’ll also set up our Authorized JavaScript origins, as well as the Authorized redirect URIs.

In the Authorized JavaScript origins input, please fill in the domain/URL that hosts your web application, for example: myapp.domain.com

Google Cloud - Authorized Domains

In the Authorized redirect URLs, fill in all the redirect URLs you’ll use to redirect users after they’ve authenticated with Google. The URL will be appended with the authorization code for access, and must have a protocol.

Google Cloud - Setup Redirect URL

Please make sure you also whitelist your localhost redirect URLs, in case this app is used during the development stage. For the sake of this example, we’ll also whitelist our localhost redirect URL.

After filling out these fields, click “Create”. Google will then open a modal, showing the Client ID and Client Secret. Make sure you copy these fields and store them somewhere safe (Usually on your .env file, as we’ll use them in our authentication flow below). You can also download the JSON containing these fields and store it in a secret manger such as 1Password.

OAuth Client Created - Modal

Please make sure you either download the JSON file containing the Client Secret or copy it and save it somewhere safe, as once the modal closes, you won’t be able to copy the Client Secret anymore.

6. Add some test users for testing purposes

When developing your app locally, you won’t be able to connect any Google Calendar Accounts unless you whitelist some test users to your app. This is due to the fact that our app is external and not approved by Google.

To add test users, follow these steps:

  1. Click the “Audience” tab.
    Google Cloud - Audience Tab
  2. Scroll until you find the “Test Users” section.
    Test Users Section
  3. Click “Add Users”, then enter the user’s email.
    Add Test users by email

7. Add the calendar scopes you plan to use

Depending on the use case you’re trying to solve by integrating Google Calendar into your app, you might want to request different scopes from the user when they authorize their Google Calendar accounts into your app.

Think of scopes as permissions you request from users to use within your app and allow your app to access private data from their Google Account. Examples of scopes are the ability to list their calendars, view their calendar events, and more.

Google splits scopes into sensitive and non-sensitive scopes. If you add sensitive scopes, you’ll need to submit your app for verification. This is also true if your app is verified and you add additional sensitive scopes.

After entering all the scopes that you need to make your app work, please make sure you enter a justification for each scope, as well as a demo video. These are required when you submit your app for verification.

To manage scopes, follow these steps:

  1. Click the "Data Access" tab.
    Data Access Tab
  2. Click "Add or remove scopes"
    Add Or Remove Scopes Button
  3. Search the scope by name or value, then add it.
    Select Scopes Section

8. Get familiar with the Google Calendar API

Now that we’ve set up the Google Client App and filled in all the information needed to allow users to connect their calendars to our app, we should get more familiar with the Google Calendar API.

I would recommend going through the Google Calendar API Overview page and navigating all the important endpoints, such as the Events or Calendar endpoints.

9. Use a Unified Calendar API service to integrate multiple providers using one API

If Google Calendar is the only calendar you’re trying to integrate into your app, then you can go ahead and skip this step. Otherwise, we recommend you use a Unified Calendar API, which would offer one API for all calendar providers.

Using a unified calendar api gives you the benefit of only having one integration for all calendar providers. In case you’d support Outlook down the line, you could add the integration without having to write any code.

Furthermore, you wouldn’t have to maintain multiple integrations, deal with breaking changes, or spend time learning the ins and outs of each calendar provider API.

Example of a Google Calendar authorization flow

The flow chart below illustrates a simple Google Calendar OAuth flow that allows users to connect their Google Calendar to your app.

Google Calendar OAuth flow

Google offers clients for Node.js, Python, etc, but for the sake of simplicity, we’ll only use HTTP calls and TypeScript to illustrate an authorization flow.

Client Side (UI)

The first part is the Client/UI side, where we’ll render a “Connect Google Calendar” button.

const googleOauthUrl = getGoogleOAuthUrl()
<button href="googleOauthUrl"   rel="noopener noreferrer"> Connect Google Calendar </button>

We recommend you use a utility function to get the Google OAuth URL, as its better for code readability, as well as passing params, such as storing client state, forcing permissions, etc.


const SCOPES = [
  "openid",
  "email",
  "<https://www.googleapis.com/auth/calendar.calendarlist>",
  "<https://www.googleapis.com/auth/calendar.events>",
  "<https://www.googleapis.com/auth/calendar.readonly>",
  // add more scopes as needed
];

export interface ClientState {
  session: Session;
  returnUrl?: string;
}

export function stateToB64(session: ClientState): string {
  return encode(JSON.stringify(session));
}

export function getGoogleOAuthUrl(
  state: ClientState,
) {
  const params = new URLSearchParams({
    client_id: process.env.GOOGLE_CLIENT_ID || "",
    redirect_uri: `${getHostName()}/api/connect/google`, // change the redirect URL as needed
    response_type: "code",
    scope: SCOPES.join(" "),
    prompt: "consent",
    access_type: "offline",
    state: stateToB64(state),
  });
  return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
}

The prompt parameter can have one of three values: none, consent, or select_account.

The consent parameter can be used in cases where the user has authorized his calendars once, but we still want Google to prompt the authorization modal once again. This might be useful when we add more scopes to our app and want users to authorise against them.

Another case for using consent is when the user hasn’t selected all the scopes required by your application, so you want to prompt the scopes again.

The select_account value can be sent to prompt the user to select an account.

The none is used to not display any authentication or consent screens.

API Side (Backend)

Next, let’s build the API handler, which is responsible for getting the code and scopes from the Google server and exchanging them for tokens.

In this example, we’re using zod for validation.

import { z } from "zod";
const successSchema = z.object({
  code: z.string(),
  scope: z.string(),
  state: z.string(),
});

const errorSchema = z.object({
  error: z.string(),
});
type ErrorParams = z.infer<typeof errorSchema>;

const querySchema = z.union([successSchema, errorSchema]);

// Handler
const googleHanlder: NextApiHandler = async (req, res) => {
  try {
    const result = querySchema.parse(req.query);
    if (isError(result)) {
      const q = new URLSearchParams({
        error: "ACCESS_DENIED",
      });
      return res.redirect(`/?${q}`);
    }

    const { session, returnUr } = stateFromB64(
      result.state
    );

    if (!hasRequiredScopes(result.scope)) {
      const q = new URLSearchParams({
        error: "MISSING_REQUIRED_PERMISSIONS",
      });
      return res.redirect(`/?${q}`);
    }

    const { access_token, refresh_token, id_token, expires_in } =
      await exchangeCodeForTokens(result.code);
    const { email } = decodeIdToken(id_token);

		// Update or insert the calendar connection, depending on your use case
    const connection = await upsertConnection(
      {
        email,
        accessToken: access_token,
        refreshToken: refresh_token,
        expiresInSeconds: expires_in,
        status: ConnectionStatus.ACTIVE,
        provider: CalendarProvider.GOOGLE,
        scopes: result.scope,
        reminderCount: 0,
        lastRemindedAt: null,
      },
      session.user
    );

    const q = new URLSearchParams({
      cid: connection.id,
    });
    if (returnUrl) q.append("returnUrl", returnUrl);

		// The API redirects back to the client side, returning the connection, or errors if any 
    res.redirect returnUrl ? returnUrl : `/calendars/google?${q}`
    );
  } catch (e: any) {
    let error = JSON.stringify(e);

      const querystr =
        typeof req.query === "string" ? req.query : JSON.stringify(req.query);
      console.error("Error in googleHandler", querystr);
      console.error("Failed to connect Google account", e);

    const q = new URLSearchParams({
      error,
    });
    return res.redirect(`/?${q}`);
  }
};

Similar to the client side, we recommend having these utility functions for exchanging code for tokens, decoding the ID token, getting the state from base 64, etc.

export async function exchangeCodeForTokens(code: string) {
  const data = new FormData();
  data.append("code", code);
  data.append("client_id", process.env.GOOGLE_CLIENT_ID || "");
  data.append("client_secret", process.env.GOOGLE_CLIENT_SECRET || "");
  data.append("redirect_uri", `${getHostName()}/api/connect/google`); // your URL
  data.append("grant_type", "authorization_code");

  try {
    const result = await fetch("<https://oauth2.googleapis.com/token>", {
      method: "POST",
      body: data,
    });

    const json = await result.json();
    if (json.error) throw json;

    const parsed = responseSchema.parse(json);
    return parsed;
  } catch (e) {
    console.error("Exchange failed");
    throw e;
  }
}

export function decodeIdToken(idToken: string) {
  const data = jwt.decode(idToken);
  if (typeof data === "string" || !data?.email) {
    throw new Error(`Could not parse id_token: ${idToken}`);
  }

  return data;
}

function isError(query: Record<string, any>): query is ErrorParams {
  return Boolean(query.error);
}

export function stateFromB64(encoded: string): ClientState {
  const str = decode(encoded);

  return JSON.parse(str) as ClientState;
}

The process.env.GOOGLE_CLIENT_ID and process.env.GOOGLE_CLIENT_SECRET environment variables we used in the implementation above are the Client ID and Client Secret mentioned in Step 5: Create your OAuth Client. Make sure to copy them when you create the OAuth Client, otherwise, you won’t be able to copy the Client Secret after closing the modal.

Google Calendar API Integration Gotchas

  • Verification can take a few weeks, so be sure to plan ahead before launch and factor this delay into your timeline. The approval process might take some time, and oftentimes, you might get rejected the first time you submit your app for verification, so I’d recommend you factor this into your timeline and make sure all stakeholders are aware of this possible delay.
  • Webhooks stop after roughly 24 hours; you should always renew them. You can register webhooks to detect changes to a calendar, but be careful to renew them, as they expire after 24 hours. You can use cron jobs to detect webhooks that expire in the next 20 minutes and renew them.
  • Only request the scopes you need. You might think that it’s a good idea to request as many scopes as possible, as you never know what’s needed in the next iteration of your app, but I recommend you only request what’s necessary for your app to function. The first reason is that Google is very thorough during the app verification process, so they will reject your app, and you’ll have to go back and forth until you get approved. The second reason is that users will be hesitant to grant access to all the scopes you require if they don’t make sense or are beyond what the app does.
  • Handle quotas and rate-limiting. Google has a per-minute per-project and per-minute per-project per-user quota. If you exceed either of them, Google returns a 403 usageLimits or 429 rateLimitExceeded code. Make sure you use exponential back-off and other tactics to make sure you don't hit those limits.
  • Careful not to log personal data. Logging data is fine, just make sure you strip out any event personal data, as events usually contain description, attendee emails, etc.

Integrate multiple calendar providers into your app using the OneCal Unified Calendar API

At OneCal, we’ve synchronized millions of calendar events between Google Calendar, Outlook, and iCloud. We know what it takes to integrate multiple calendar providers, learn the specifics of each API, and make each implementation work seamlessly.

That's why we're building a Unified Calendar API product that supports Outlook, Google Calendar, and iCloud Calendar out of the box, allowing you to integrate all major providers using a single robust, easy-to-use API.

Join our Unified Calendar API waitlist to get notified as soon as we launch the new product.