# How Google Calendar recurring events work

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

Google Calendar stores repeating meetings as a parent event plus generated instances. The parent carries the recurrence rule, while moved or cancelled occurrences become exceptions. That model is powerful, but the native Google Calendar API exposes several concepts you have to stitch together: `recurrence`, `recurringEventId`, `originalStartTime`, cancelled instances, and expanded date windows.

Nylas keeps the same calendar semantics but gives you one Events API shape across Google, Microsoft, iCloud, and Exchange. This recipe shows the Google behavior you need to understand, then maps it to the Nylas request pattern.

## Create a recurring Google Calendar event

Create a recurring event by sending a [Create Event request](/docs/reference/api/events/create-event/) with a `recurrence` array. The array usually has one recurrence rule string. The `when` object sets the first occurrence and the time zone that anchors the whole series.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "title": "Weekly product review",
    "busy": true,
    "when": {
      "start_time": 1782918000,
      "end_time": 1782921600,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    },
    "participants": [
      { "email": "maya@example.com", "name": "Maya Patel" }
    ],
    "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
  }'
```

```js [googleRecurring-Node.js SDK]
const event = await nylas.events.create({
  identifier: grantId,
  queryParams: { calendarId: "primary" },
  requestBody: {
    title: "Weekly product review",
    busy: true,
    when: {
      startTime: 1782918000,
      endTime: 1782921600,
      startTimezone: "America/New_York",
      endTimezone: "America/New_York",
    },
    participants: [{ email: "maya@example.com", name: "Maya Patel" }],
    recurrence: ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"],
  },
});
```

```python [googleRecurring-Python SDK]
event = nylas.events.create(
    identifier=grant_id,
    query_params={"calendar_id": "primary"},
    request_body={
        "title": "Weekly product review",
        "busy": True,
        "when": {
            "start_time": 1782918000,
            "end_time": 1782921600,
            "start_timezone": "America/New_York",
            "end_timezone": "America/New_York",
        },
        "participants": [{"email": "maya@example.com", "name": "Maya Patel"}],
        "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"],
    },
)
```

Use `calendar_id=primary` for the connected user's default Google Calendar, or pass a specific calendar ID from the [List Calendars endpoint](/docs/reference/api/calendar/get-all-calendars/).

## Write the recurrence rule

Google Calendar follows the recurrence rule format used by major calendar providers. Nylas passes the recurrence string through the unified Events API, so the same rule works on other providers when they support the pattern.

| Schedule | Recurrence rule |
| --- | --- |
| Every Tuesday | `RRULE:FREQ=WEEKLY;BYDAY=TU` |
| Every weekday | `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` |
| Every other week on Tuesday | `RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU` |
| First Monday of each month | `RRULE:FREQ=MONTHLY;BYDAY=1MO` |
| Stop after 12 meetings | `RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12` |
| Stop on a date | `RRULE:FREQ=WEEKLY;BYDAY=TU;UNTIL=20261231T235959Z` |

To skip one occurrence when you create the series, include an `EXDATE` entry in the same array:

```json
{
  "recurrence": [
    "RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12",
    "EXDATE:20260707T150000Z"
  ]
}
```

For the full recurrence reference, including provider differences, see [Recurring events and recurrence rules](/docs/v3/calendar/recurring-events/).

## List recurring Google Calendar instances

When you list events without a date range, Google returns the parent recurring event and its recurrence rule. When you pass `start` and `end`, Nylas expands the series into the instances inside that window.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary&start=1782892800&end=1785571200' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

Expanded instances share a `master_event_id` that points back to the parent series. Store both IDs in your app. The instance `id` lets you move or cancel one meeting, while the parent `id` lets you edit the whole series.

## Update one instance or the whole series

To update one Google Calendar occurrence, send an [Update Event request](/docs/reference/api/events/put-events-id/) using the occurrence ID from the expanded list. Nylas updates only that instance, and the response includes `original_start_time` so you can tell which generated occurrence changed.

```bash
curl --request PUT \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events/<INSTANCE_EVENT_ID>?calendar_id=primary' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "when": {
      "start_time": 1783008000,
      "end_time": 1783011600,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    }
  }'
```

To update every future instance, update the parent series instead. Google keeps existing overrides that still fit the new rule. Microsoft can remove overrides when a recurrence pattern changes. If you support more than Google, test against the stricter Microsoft behavior before exposing broad recurrence editing in your UI.

## Cancel one occurrence

Delete one expanded instance to cancel that date:

```bash
curl --request DELETE \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events/<INSTANCE_EVENT_ID>?calendar_id=primary' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

Google often represents a deleted occurrence as a cancelled event instead of only adding an `EXDATE` to the parent. Treat cancelled instances as tombstones in your sync table, not as events to show on the calendar.

## Things to know about Google recurrence

**Always pass a time zone.** Without `start_timezone` and `end_timezone`, the series falls back to Coordinated Universal Time. A weekly 9:00 AM meeting can appear to drift around daylight saving time if you omit the time zone.

**Bound every list request.** Open-ended recurrence can generate years of instances. Pass `start` and `end` when you need expanded occurrences, and keep the window close to what your UI or sync job needs.

**Invites can fan out.** Updating a parent event sends participant updates for the whole series. Use instance-level updates when only one meeting changed.

**Use webhooks for follow-up sync.** Subscribe to `event.created`, `event.updated`, and `event.deleted` so moved or cancelled Google occurrences reach your app without polling. See [Google Calendar webhooks](/docs/cookbook/calendar/google-calendar-webhooks/).

## What's next

- [Create Google calendar events](/docs/cookbook/calendar/events/create-events-google/) for single events, participants, Google Meet, and write scopes
- [Recurring events and recurrence rules](/docs/v3/calendar/recurring-events/) for the full recurrence behavior
- [Google Calendar API pagination and sync](/docs/cookbook/calendar/google-calendar-api-pagination/) for `nextPageToken`, sync windows, and incremental sync concepts
- [Sync calendar events with webhooks](/docs/cookbook/calendar/calendar-webhooks/) for the unified webhook setup