Module · The outside world
Google Calendar
Lesson 15 of 21 · 6 min
Up to now every node has done one thing. This one does six, and which six fields you see depends on a dropdown at the top. Book a meeting, move it, cancel it, list what is coming up, fetch one event, or ask whether a slot is free — all the same node, wearing different shapes.
That pattern is worth meeting properly, because most of the integration nodes work this way. You are not learning a calendar node; you are learning to read a node whose configuration is conditional on one field.
The other thing to know before anything else: this node is a tool. An AI agent can call it directly, which is how a voice agent ends a conversation with a meeting genuinely in the diary rather than a promise to send something later.
The six operations
| Operation | What it is for |
|---|---|
create_event | Book something. The write path — title, times, attendees, notifications. |
check_availability | Ask whether a window is free, for a calendar and a set of attendees, without listing what is in it. |
get_events | List what is in a range, with searching, filtering and sorting. |
get_event | One event in full, by id. |
update_event · delete_event | Change or remove an existing event, by id. Both need an id you kept from earlier. |
create_event
- What it is for
- Book something. The write path — title, times, attendees, notifications.
check_availability
- What it is for
- Ask whether a window is free, for a calendar and a set of attendees, without listing what is in it.
get_events
- What it is for
- List what is in a range, with searching, filtering and sorting.
get_event
- What it is for
- One event in full, by id.
update_event · delete_event
- What it is for
- Change or remove an existing event, by id. Both need an id you kept from earlier.
| Field | What it holds |
|---|---|
operation | Calendar operation to perform One of: create_event · update_event · delete_event · get_events · get_event · check_availability Defaults to create_event. |
event_details | — |
attendees | List of attendee email addresses |
options | — |
time_min | For get_events: start of the date range to query. Accepts a fixed datetime or a template variable like {{node.start}}. Leave empty for today. |
time_max | For get_events: end of the date range to query. Accepts a fixed datetime or a template variable like {{node.end}}. Leave empty for +30 days. |
max_results | For get_events: maximum number of events to return. Defaults to 50. |
query | For get_events: free text search query (matches title, description, location, attendees by default — see match_scope to restrict). |
match_scope | For get_events: restrict the query match to a specific field. 'any' (default) uses Google's broad match. The others post-filter results to only that field. One of: any · title · description · location · attendees Defaults to any. |
sort_by | For get_events: sort returned events. start_asc=earliest start first (default), start_desc=latest start first, end_asc=earliest end first, end_desc=latest end first. One of: start_asc · start_desc · end_asc · end_desc Defaults to start_asc. |
operation
- What it holds
- Calendar operation to perform One of:
create_event · update_event · delete_event · get_events · get_event · check_availabilityDefaults tocreate_event.
event_details
- What it holds
- —
attendees
- What it holds
- List of attendee email addresses
options
- What it holds
- —
time_min
- What it holds
- For get_events: start of the date range to query. Accepts a fixed datetime or a template variable like {{node.start}}. Leave empty for today.
time_max
- What it holds
- For get_events: end of the date range to query. Accepts a fixed datetime or a template variable like {{node.end}}. Leave empty for +30 days.
max_results
- What it holds
- For get_events: maximum number of events to return. Defaults to
50.
query
- What it holds
- For get_events: free text search query (matches title, description, location, attendees by default — see match_scope to restrict).
match_scope
- What it holds
- For get_events: restrict the query match to a specific field. 'any' (default) uses Google's broad match. The others post-filter results to only that field. One of:
any · title · description · location · attendeesDefaults toany.
sort_by
- What it holds
- For get_events: sort returned events. start_asc=earliest start first (default), start_desc=latest start first, end_asc=earliest end first, end_desc=latest end first. One of:
start_asc · start_desc · end_asc · end_descDefaults tostart_asc.
Writes go in event_details. Title, description, location, start and end, timezone and which calendar — all inside that object. attendees sits outside it as a flat list of addresses, and options holds the three switches that decide what happens as a side effect of booking.
A worked example
A contact is created. Before anything is booked, the workflow asks whether the slot is free — and only books if it is.
Ask first, then book
The same node type twice, doing two different jobs.
Scroll for all 5 steps →
Read the two calendar nodes side by side. They are the same type and they share almost no fields, because operation changed what the node is. The first takes a window and a list of people; the second takes an event.
The condition between them is the part worth copying. check_availability returning nothing means nothing is in the way, so is_empty on its events is the “go ahead” test. Booking without that check is how two things end up at ten o'clock.
Both of the interesting options default to on. send_notifications emails every attendee, and create_meeting_link mints a Google Meet link. So the default behaviour of creating an event is to mail people and start a video call they did not ask for. That is usually what you want for a booked meeting and never what you want for an internal reminder — turn both off when the event is a note to yourself.
Reading the calendar rather than writing to it
get_events is the operation people underuse. Leave time_min and time_max empty and it covers today to thirty days out, which is the range you wanted anyway. Then three fields shape the result:
querywithmatch_scope— a full-text filter, optionally restricted to just the title, the description, the location or the attendees. Searching “kickoff” across everything and searching it in titles only give very different lists.attendee_filter— keep only events involving a particular person, independent of the query. This is the one that answers “what am I doing with this customer”.sort_by— earliest start for what is coming, latest start for what just happened. The second is the one to use when you are looking backwards.
max_results defaults to 50 and goes to 2500. If you are near the top of that you are building a report, and a report should probably be reading your own CRM rather than a calendar.
What comes back
| Reference | What you get |
|---|---|
{{alias.event_id}} | The id of what you just created. Keep it — update and delete need it, and there is no other way back to the event. |
{{alias.event_link}} | The link to the event. What you put in the confirmation message. |
{{alias.events}} | The array from a query — or from an availability check, where empty means free. |
{{alias.conference_data}} | The Meet details, when a link was created. |
{{alias.event_id}}
- What you get
- The id of what you just created. Keep it — update and delete need it, and there is no other way back to the event.
{{alias.event_link}}
- What you get
- The link to the event. What you put in the confirmation message.
{{alias.events}}
- What you get
- The array from a query — or from an availability check, where empty means free.
{{alias.conference_data}}
- What you get
- The Meet details, when a link was created.
What breaks
Update and delete need an id you did not keep. Nothing in a later run remembers what an earlier run booked. If a workflow might ever need to move or cancel the meeting, write {{alias.event_id}} onto the record it belongs to — the deal, the contact, the task — at the moment you create it. Retrofitting this is a data-repair job.
Timezones are set in two places and only one of them is obvious. The event carries its own timezone in event_details, and the connected Google account has one of its own. A time written without an offset is interpreted against the node's timezone, not the attendee's — so an ISO string with the offset in it, as in the workflow above, is the version that cannot be misread.
An availability check is not a lock. Between the check and the create, anything can happen — including another run of the same workflow. For a calendar with occasional bookings that is fine. For a booking system taking requests in parallel it is not, and the fix is not in this node; it is to serialise the booking upstream.
Try it
- Connect a Google account, add one node, and run
get_eventswith both dates left empty. Read what comes back — that is your next thirty days. - Add a
queryand setmatch_scopeto title. Compare the result with the same query on “any field”. - Switch a second node to
create_eventand book something ten minutes from now, with both options inoptionsturned off. Nothing should be emailed and no Meet link should appear. - Book it again with both on, and watch what arrives in the attendee's inbox. The difference is the argument for reading defaults.
- Finally run
check_availabilityover a slot you know is busy and confirm you get events back — then over a free one and confirm you get none. That empty result is what your condition branches on.
Next: Google Sheets — the same integration pattern, and the node people reach for when the answer needs to leave Expedify and land somewhere a colleague can open.

