# How to create Google calendar events

Source: https://developer.nylas.com/docs/cookbook/calendar/events/create-events-google/

Creating events on Google Calendar through the native API means dealing with Google's restricted scope requirements right away. Unlike reading events, which only needs a sensitive scope, write access requires the `calendar` scope, classified as restricted, and that triggers a full third-party security assessment before your app can go to production. On top of that, Google has its own conferencing auto-creation behavior, event type restrictions, and color ID system that you'll need to account for.

Nylas gives you a single [Events API](/docs/reference/api/events/) that handles event creation across Google, Microsoft, iCloud, and Exchange. This guide walks through creating events on Google Calendar accounts and covers the Google-specific behavior you should know about.

## Why use Nylas instead of the Google Calendar API directly?

Writing to Google Calendar introduces more friction than most developers expect:

- **Restricted scope required for writes** - Reading events uses the sensitive `calendar.events.readonly` scope, but creating events requires the restricted `calendar` scope. That means a third-party security assessment before you can launch.
- **Google Meet auto-creation** - Google's conferencing auto-attach behavior is provider-specific. Setting it up through the native API requires understanding `conferenceData` and `createRequest` fields that don't exist on other providers.
- **Event type restrictions** - You can't create `focusTime`, `outOfOffice`, or `workingLocation` events through any API. These are managed exclusively through the Google Calendar UI.
- **Provider-specific fields** - Color IDs, room resources, and event visibility settings all work differently on Google than on Microsoft or iCloud.

If Google Calendar is your only target and you want full control over every Google-specific field, the native API works. But if you need multi-provider support or want to avoid the security assessment process, Nylas is the faster path to production.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for a Google Calendar or Google Workspace account
- The appropriate [Google OAuth scopes](/docs/provider-guides/google/) configured in your GCP project, including write access


> **Info:** 
> **New to Nylas?** Start with the [quickstart guide](/docs/v3/getting-started/) to set up your app and connect a test account before continuing here.


### Google OAuth scopes for write access

Creating events requires the `calendar` scope, which Google classifies as restricted. This is a step up from what you need to just read events:

| Scope tier    | Example                             | What's required                                   |
| ------------- | ----------------------------------- | ------------------------------------------------- |
| Non-sensitive | `calendar.readonly` (metadata only) | No verification needed                            |
| Sensitive     | `calendar.events.readonly`          | OAuth consent screen verification                 |
| Restricted    | `calendar.events`, `calendar`       | Full security assessment by a third-party auditor |

The `calendar.events` scope is enough for creating and modifying events, but most apps use the broader `calendar` scope to also manage calendars. Both are restricted and require a [security assessment](/docs/provider-guides/google/google-verification-security-assessment-guide/) before production use.

Nylas handles token refresh and scope management, but your GCP project still needs the correct scopes configured. See the [Google provider guide](/docs/provider-guides/google/) for the full setup.

## Create an event

Make a [Create Event request](/docs/reference/api/events/create-event/) with the grant ID and a `calendar_id` query parameter. You can use `primary` to target the user's default calendar.

> **Error:** 
> **You're about to send a real event invite!** The code samples below send an email from the connected account to any email addresses in the `participants` field. Make sure you actually want to invite those addresses before running this.

```bash
curl --compressed --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "title": "Annual Philosophy Club Meeting",
    "busy": true,
    "conferencing": {
        "provider": "Zoom Meeting",
        "autocreate": {
          "conf_grant_id": "<NYLAS_GRANT_ID>",
          "conf_settings": {
            "settings": {
              "join_before_host": true,
              "waiting_room": false,
              "mute_upon_entry": false,
              "auto_recording": "none"
            }
          }
        }
      },
    "participants": [
      {
        "name": "Leyah Miller",
        "email": "leyah@example.com"
      },
      {
        "name": "Nyla",
        "email": "nyla@example.com"
      }
    ],
    "resources": [{
      "name": "Conference room",
      "email": "conference-room@example.com"
    }],
    "description": "Come ready to talk philosophy!",
    "when": {
      "start_time": 1674604800,
      "end_time": 1722382420,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    },
    "location": "New York Public Library, Cave room",
    "recurrence": [
      "RRULE:FREQ=WEEKLY;BYDAY=MO",
      "EXDATE:20211011T000000Z"
  ],
}'

```

```json
{
  "request_id": "1",
  "data": {
    "busy": true,
    "calendar_id": "primary",
    "conferencing": {
      "details": {
        "meeting_code": "<MEETING_CODE>",
        "url": "<MEETING_URL>"
      },
      "provider": "Google Meet"
    },
    "created_at": 1701974804,
    "creator": {
      "email": "leyah@example.com",
      "name": "Leyah Miller"
    },
    "description": null,
    "grant_id": "<NYLAS_GRANT_ID>",
    "hide_participants": false,
    "html_link": "<EVENT_LINK>",
    "ical_uid": "6aaaaaaame8kpgcid6hvd0q@google.com",
    "id": "<NYLAS_EVENT_ID>",
    "object": "event",
    "organizer": {
      "email": "leyah@example.com",
      "name": "Leyah Miller"
    },
    "participants": [
      {
        "email": "jenna.doe@example.com",
        "status": "yes"
      },
      {
        "email": "leyah@example.com",
        "status": "yes"
      }
    ],
    "read_only": true,
    "reminders": {
      "overrides": null,
      "use_default": true
    },
    "status": "confirmed",
    "title": "Holiday check in",
    "updated_at": 1701974915,
    "when": {
      "end_time": 1701978300,
      "end_timezone": "America/Los_Angeles",
      "object": "timespan",
      "start_time": 1701977400,
      "start_timezone": "America/Los_Angeles"
    }
  }
}


```

```js [createEvents-Node.js SDK]

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
});

const now = Math.floor(Date.now() / 1000); // Time in Unix timestamp format (in seconds)

async function createAnEvent() {
  try {
    const event = await nylas.events.create({
      identifier: "<NYLAS_GRANT_ID>",
      requestBody: {
        title: "Build With Nylas",
        when: {
          startTime: now,
          endTime: now + 3600,
        },
      },
      queryParams: {
        calendarId: "<CALENDAR_ID>",
      },
    });

    console.log("Event:", event);
  } catch (error) {
    console.error("Error creating event:", error);
  }
}

createAnEvent();


```

```python [createEvents-Python SDK]

from nylas import Client

nylas = Client(
    "<NYLAS_API_KEY>",
    "<NYLAS_API_URI>"
)

grant_id = "<NYLAS_GRANT_ID>"

events = nylas.events.create(
  grant_id,
  request_body={
    "title": 'Build With Nylas',
    "when": {
      "start_time": 1609372800,
      "end_time": 1609376400
    },
  },
  query_params={
    "calendar_id": "<CALENDAR_ID>"
  }
)

print(events)

```


```ruby [createEvents-Ruby SDK]

require 'nylas'
require 'date'

nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")

query_params = {
  calendar_id: "<CALENDAR_ID>"
}

today = Date.today
start_time = Time.local(today.year, today.month, today.day, 13, 0, 0).to_i
end_time = Time.local(today.year, today.month, today.day, 13, 30, 0).to_i

request_body = {
  when: {
    start_time: start_time,
    end_time: end_time
  },
  title: "Let's learn some Nylas Ruby SDK!",
  location: "Nylas' Headquarters",
  description: "Using the Nylas API with the Ruby SDK is easy.",
  participants: [{
    name: "Blag",
    email: "atejada@gmail.com",
    status: 'noreply'
  }]
}

event, _request_id = nylas.events.create(
  identifier: "<NYLAS_GRANT_ID>",
  query_params: query_params,
  request_body: request_body
)

puts event


```

```java [createEvents-Java SDK]

import com.nylas.NylasClient;
import com.nylas.models.*;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;

public class create_calendar_events {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    // Get today's date
    LocalDate today = LocalDate.now();

    // Set time. Because we're using UTC we need to add the hours in difference from our own timezone.
    Instant sixPmUtc = today.atTime(13, 0).toInstant(ZoneOffset.UTC);

    // Set the date and time for the event. We add 30 minutes to the starting time.
    Instant sixPmUtcPlus = sixPmUtc.plus(30, ChronoUnit.MINUTES);

    // Get the Date and Time as a Unix timestamp
    long startTime = sixPmUtc.getEpochSecond();
    long endTime = sixPmUtcPlus.getEpochSecond();

    // Define title, location, and description of the event
    String title = "Let's learn some about the Nylas Java SDK!";
    String location = "Nylas Headquarters";
    String description = "Using the Nylas API with the Java SDK is easy.";

    // Create the timespan for the event
    CreateEventRequest.When.Timespan timespan = new CreateEventRequest.
        When.Timespan.
        Builder(Math.toIntExact(startTime), Math.toIntExact(endTime)).
        build();

    // Create the list of participants.
    List<CreateEventRequest.Participant> participants_list = new ArrayList<>();

    participants_list.add(new CreateEventRequest.
        Participant("johndoe@example.com", ParticipantStatus.NOREPLY,
        "John Doe", "", ""));

    // Build the event details.
    CreateEventRequest createEventRequest = new CreateEventRequest.Builder(timespan)
        .participants(participants_list)
        .title(title)
        .location(location)
        .description(description)
        .build();

    // Build the event parameters. In this case, the Calendar ID.
    CreateEventQueryParams createEventQueryParams = new CreateEventQueryParams.Builder("<CALENDAR_ID>").build();

    // Create the event itself
    Event event = nylas.events().create(
        "<NYLAS_GRANT_ID>",
        createEventRequest,
        createEventQueryParams).getData();
  }
}

```

```kt [createEvents-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.*

import java.time.LocalDateTime
import java.time.ZoneOffset

fun main(args: Array<String>) {
  val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
  var startDate = LocalDateTime.now()

  // Set the time. Because we're using UTC, we need to add the difference in hours from our own timezone.
  startDate = startDate.withHour(13);
  startDate = startDate.withMinute(0);
  startDate = startDate.withSecond(0);
  val endDate = startDate.withMinute(30);

  // Convert the dates from Unix timestamp format to integer.
  val iStartDate: Int = startDate.toEpochSecond(ZoneOffset.UTC).toInt()
  val iEndDate: Int = endDate.toEpochSecond(ZoneOffset.UTC).toInt()

  // Create the timespan for the event.
  val eventWhenObj: CreateEventRequest.When = CreateEventRequest.When.
  Timespan(iStartDate, iEndDate);

  // Define the title, location, and description of the event.
  val title: String = "Let's learn about the Nylas Kotlin/Java SDK!"
  val location: String = "Blag's Den!"
  val description: String = "Using the Nylas API with the Kotlin/Java SDK is easy."

  // Create the list of participants.
  val participants: List<CreateEventRequest.Participant> = listOf(CreateEventRequest.
      Participant("<PARTICIPANT_EMAIL>", ParticipantStatus.NOREPLY, "<PARTICIPANT_NAME>"))

  // Create the event request. This adds date/time, title, location, description, and participants.
  val eventRequest: CreateEventRequest = CreateEventRequest(eventWhenObj, title, location, description, participants)

  // Set the event parameters.
  val eventQueryParams: CreateEventQueryParams = CreateEventQueryParams("<CALENDAR_ID>")

  val event: Response<Event> = nylas.events().create("<NYLAS_GRANT_ID>",
      eventRequest, eventQueryParams)
}

```


Nylas returns the created event with an `id` you can use for subsequent updates or deletions. The same code works for Microsoft, iCloud, and Exchange accounts with no provider-specific changes.

## Create Google events from the terminal

Writing to Google Calendar needs the restricted `calendar` scope, which triggers a full security assessment before production. The [Nylas CLI](https://cli.nylas.com/docs/commands) skips the native API entirely: `nylas calendar events create` schedules an event and invites attendees in one command, with no GCP project of your own.


The Nylas CLI creates events from your terminal through the same Events API. After `nylas init` and `nylas auth login`, `calendar events create` schedules an event and invites attendees in one command. Start and end times use the `YYYY-MM-DD HH:MM` format:

```bash
nylas calendar events create \
  --title "Design review" \
  --start "2026-06-25 14:00" \
  --end "2026-06-25 15:00" \
  --participant alice@example.com \
  --participant bob@example.com \
  --location "Conference Room A"
```

Each `--participant` adds an attendee who receives a normal calendar invitation; repeat the flag for more. See the [`calendar events create`](https://cli.nylas.com/docs/commands/calendar-events-create) command reference for every flag, including `--description` for the event body and `--location` for the venue.


Add conferencing and Google auto-creates a Google Meet link, while every participant receives a normal invitation. The `--start` and `--end` values use 24-hour `YYYY-MM-DD HH:MM`, so the example above books an event 60 minutes long. Recurring events use the standard iCalendar rule rather than Google's custom format.

## Add participants and send invitations

The `notify_participants` query parameter controls whether Google sends email invitations to people listed in the `participants` array. It defaults to `true`, so participants receive calendar invitations automatically unless you explicitly disable it.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary&notify_participants=true" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "title": "Project sync",
    "when": {
      "start_time": 1700000000,
      "end_time": 1700003600
    },
    "participants": [
      { "email": "colleague@example.com" }
    ]
  }'
```

> **Warn:** 
> **When `notify_participants=false`**, Google creates the event on the organizer's calendar only. Participants don't receive an email invitation or an ICS file, and the event does not appear on their calendars.

## Things to know about Google

A few provider-specific details that matter when creating events on Google Calendar and Google Workspace accounts.

### Restricted scope required for writes

This is the biggest difference from reading events. Any operation that creates, updates, or deletes events needs the `calendar` or `calendar.events` scope, both of which are restricted. Google requires a third-party security assessment before your app can request these scopes in production. During development, you can use the scopes with test users, but plan for the assessment timeline (it can take several weeks) before launching.

See the [security assessment guide](/docs/provider-guides/google/google-verification-security-assessment-guide/) for details on what the process involves.

### Google Meet auto-creation

You can automatically generate a Google Meet link when creating an event by including `conferencing.autocreate` in your request body:

```json
{
  "conferencing": {
    "provider": "Google Meet",
    "autocreate": {}
  }
}
```

No extra OAuth scopes are needed for Google Meet auto-creation since conferencing is considered part of the event. You can also manually attach a Meet, Zoom, or Microsoft Teams link by passing the `conferencing.details` object instead. See the [conferencing guide](/docs/v3/calendar/add-conferencing/) for all the options.

### Event types are read-only

Google Calendar supports special event types like `focusTime`, `outOfOffice`, and `workingLocation`, but you can't create these through any API. They're managed exclusively through the Google Calendar UI. The Events API only creates `default` type events. If your app needs to display these special types, you can [read them](/docs/cookbook/calendar/events/list-events-google/#filter-by-event-type) from existing calendars, but you can't programmatically create them.

### Color IDs

Google supports numeric color IDs for event-level color overrides. Pass a string value from `"1"` through `"11"` in the event's `color_id` field to set the color. These map to Google Calendar's fixed color palette. Other providers handle event colors differently or not at all, so don't rely on this field if you're building for multiple providers.

### All-day events

To create an all-day event, use the `datespan` format in the `when` object instead of `start_time`/`end_time`. The end date is exclusive, meaning it should be the day after the last day of the event:

```json
{
  "when": {
    "start_date": "2025-06-15",
    "end_date": "2025-06-16"
  }
}
```

A two-day event on June 15-16 would have `end_date` set to `"2025-06-17"`. This matches the iCalendar spec and Google's own behavior, but it catches people off guard.

### Room resources

Google Workspace accounts support booking meeting rooms by including room resource email addresses in the `resources` field. Rooms must belong to the user's Google Workspace organization. Personal Google accounts don't have access to room resources.

```json
{
  "resources": [
    {
      "email": "conference-room@resource.calendar.google.com"
    }
  ]
}
```

### Recurring events

You can create recurring events by including an `recurrence` array with RRULE strings. Google keeps existing overrides when you modify a recurrence pattern, which is different from Microsoft where overrides get removed on pattern changes. For all the details on creating and managing recurring events, see the [recurring events guide](/docs/v3/calendar/recurring-events/).

### Rate limits

Google enforces calendar API quotas at two levels:

- **Per-user:** Each authenticated user has per-minute and daily limits for API calls
- **Per-project:** Your GCP project has an overall daily limit across all users

Write operations are more heavily rate-limited than reads. If your app creates events for many users, you'll hit project quotas faster than you might expect. Use [webhooks](/docs/v3/notifications/) instead of polling to track event changes, and consider setting up [Google Pub/Sub](/docs/provider-guides/google/connect-google-pub-sub/) for real-time sync with lower latency.

## What's next

- [Events API reference](/docs/reference/api/events/) for full endpoint documentation and all available parameters
- [Using the Events API](/docs/v3/calendar/using-the-events-api/) for updating and deleting events
- [List Google events](/docs/cookbook/calendar/events/list-events-google/) for retrieving events from Google Calendar accounts
- [Add conferencing](/docs/v3/calendar/add-conferencing/) to attach Google Meet, Zoom, or Teams links to events
- [Recurring events](/docs/v3/calendar/recurring-events/) for creating and managing repeating events
- [Availability](/docs/v3/calendar/calendar-availability/) to check free/busy status before creating events
- [Webhooks](/docs/v3/notifications/) for real-time notifications instead of polling
- [Google Pub/Sub](/docs/provider-guides/google/connect-google-pub-sub/) for real-time sync with Google accounts
- [Google provider guide](/docs/provider-guides/google/) for full Google setup including OAuth scopes and verification
- [Google verification and security assessment](/docs/provider-guides/google/google-verification-security-assessment-guide/), required for restricted scopes in production