Skip to content
Expedify
Actions — making things happen

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

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.
Ten of seventeen. Which of them the panel shows depends entirely on the first row.

operation

What it holds
Calendar operation to perform One of: create_event · update_event · delete_event · get_events · get_event · check_availability Defaults to create_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 · attendees Defaults to any.

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_desc Defaults to start_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.

successYesNo

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:

  • query with match_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

{{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

  1. Connect a Google account, add one node, and run get_events with both dates left empty. Read what comes back — that is your next thirty days.
  2. Add a query and set match_scope to title. Compare the result with the same query on “any field”.
  3. Switch a second node to create_event and book something ten minutes from now, with both options in options turned off. Nothing should be emailed and no Meet link should appear.
  4. Book it again with both on, and watch what arrives in the attendee's inbox. The difference is the argument for reading defaults.
  5. Finally run check_availability over 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.