Property Reference
This is the complete reference for all properties and methods available when writing gate functions. Consult this page while working in the Code Editor or reviewing code generated by the AI Generator.
GateEvent Properties
Every gate function receives a GateEvent object. It contains the event's core data plus computed convenience properties that simplify common checks.
Core Properties
These are the fundamental properties of every calendar event:
| Property | Type | Description | Example Value |
|---|---|---|---|
title | string | Event title/summary | "Team Standup" |
description | string | null | Event description/notes | "Daily sync meeting" |
location | string | null | Event location | "Conference Room A" |
start | { dateTime: string, timeZone: string | null } | Event start time (ISO 8601) | { dateTime: "2026-02-14T14:00:00Z", timeZone: "America/New_York" } |
end | { dateTime: string, timeZone: string | null } | Event end time (ISO 8601) | { dateTime: "2026-02-14T15:00:00Z", timeZone: "America/New_York" } |
isAllDay | boolean | Whether the event is an all-day event | false |
status | 'confirmed' | 'tentative' | 'cancelled' | Event lifecycle status, set by the organizer for everyone (not your RSVP — see responseStatus) | "confirmed" |
responseStatus | 'accepted' | 'declined' | 'tentative' | 'needsAction' | 'organizer' | null | Your RSVP to the event (the connected calendar owner's response). null when you are not an attendee or the source calendar does not expose a response. | "needsAction" |
showAs | 'free' | 'busy' | null | Free/busy status | "busy" |
attendees | Array<{ email: string, displayName?: string, status?: 'NEEDS-ACTION' | 'ACCEPTED' | 'DECLINED' | 'TENTATIVE' }> | List of event attendees. Each attendee's status is their own RSVP (iCalendar PARTSTAT), present when the source calendar exposes it. | [{ email: "alice@example.com", displayName: "Alice", status: "ACCEPTED" }] |
organizer | { email: string, displayName?: string } | null | Event organizer | { email: "bob@example.com", displayName: "Bob" } |
recurringEventId | string | null | The series id when this event is one occurrence of a recurring series; null for one-off events | "5cq2mq3l9tid2sent85pu0dqp4" |
type | 'default' | 'outOfOffice' | The kind of event | "default" |
The type property reflects the event kind as reported by the source calendar provider:
| Source | Value |
|---|---|
| Google out-of-office event | "outOfOffice" |
| Google focus time / working location / birthday / normal | "default" |
| Outlook event shown as "Out of office" | "outOfOffice" |
| Outlook anything else | "default" |
| Apple/CalDAV, ICS | "default" |
Filtering by RSVP status
responseStatus reflects your own response to the event (the response on the connected source calendar), not the responses of other attendees. Use it to keep invitations you haven't accepted off your synced calendar:
function rule(event: GateEvent): GateResult {
// Drop invitations you haven't accepted (declined or not yet responded)
if (
event.responseStatus === 'declined' ||
event.responseStatus === 'needsAction'
) {
return { pass: false, reason: 'Invitation not accepted' };
}
return { pass: true };
}
How each provider maps onto responseStatus:
responseStatus | Microsoft | Apple / CalDAV / ICS (PARTSTAT) | |
|---|---|---|---|
accepted | accepted | accepted | ACCEPTED |
declined | declined | declined | DECLINED |
tentative | tentative | tentativelyAccepted | TENTATIVE |
needsAction | needsAction | notResponded | NEEDS-ACTION |
organizer | you organize it | organizer | you are the ORGANIZER |
null | not an attendee | none / not invited | not listed as an attendee |
For ICS feeds, CalendarPipe matches the email you entered when adding the feed against each event's ATTENDEE list. Availability-only (busy/free) publishes carry no attendees, so responseStatus stays null for those feeds — that is expected, not a missing RSVP.
A null value means there is no RSVP to evaluate — for example a personal event you created with no other invitees. Account for null explicitly if your rule should treat un-invited events differently from accepted ones.
status vs responseStatus vs attendees[].status
There are three RSVP-adjacent fields, and it's easy to confuse them:
status | responseStatus | attendees[].status | |
|---|---|---|---|
| Describes | The event's own lifecycle state | Your RSVP to the event | Each invitee's RSVP |
| Set by | The organizer — the same for everyone | You — yours alone | Each attendee individually |
| Answers | "Is this event happening?" | "Am I going?" | "Who else is going?" |
| Values | confirmed | tentative | cancelled | accepted | declined | tentative | needsAction | organizer | null | 'NEEDS-ACTION' | 'ACCEPTED' | 'DECLINED' | 'TENTATIVE' |
So a confirmed team meeting you were invited to but never answered is status: 'confirmed' and responseStatus: 'needsAction'.
responseStatus uses lowercase values ('accepted'), while attendees[].status uses the uppercase iCalendar PARTSTAT vocabulary ('ACCEPTED'). They describe the same idea from different vantage points — your own response vs. a specific attendee's — but responseStatus adds 'organizer'/null, and attendees[].status is omitted entirely when the source calendar doesn't expose it.
Watch out for 'tentative', which appears in both with different meanings:
status === 'tentative'— the event itself is tentatively scheduled (the organizer hasn't firmed it up).responseStatus === 'tentative'— you replied "maybe".
The two compose naturally — use status to drop cancelled events and responseStatus to drop invitations you haven't accepted:
function rule(event: GateEvent): GateResult {
if (event.status === 'cancelled') {
return { pass: false, reason: 'Event cancelled' };
}
if (
event.responseStatus === 'declined' ||
event.responseStatus === 'needsAction'
) {
return { pass: false, reason: 'Not accepted' };
}
return { pass: true };
}
On Google the two are cleanly separated: status comes straight from the event and responseStatus from your own attendee entry. On Microsoft, status is derived (cancelled events, plus free/tentative showAs, are mapped to tentative), so responseStatus is the more reliable signal for "did I accept?".
Computed Convenience Properties
These are read-only properties computed from the core properties. They simplify common checks so you don't have to parse dates or count arrays yourself.
| Property | Type | Description | Example Value |
|---|---|---|---|
startDate | string | Alias for start.dateTime (ISO 8601) | "2026-02-14T14:00:00Z" |
endDate | string | Alias for end.dateTime (ISO 8601) | "2026-02-14T15:00:00Z" |
durationMinutes | number | Event duration in minutes | 60 |
dayOfWeek | number | Day of week (0 = Sunday, 6 = Saturday), in UTC | 1 (Monday) |
hour | number | Hour of day the event starts (0-23), in UTC | 14 (2 PM UTC) |
isWeekday | boolean | Whether the event falls on Monday-Friday, in UTC | true |
attendeeCount | number | Number of attendees | 5 |
Computed properties are derived from core properties at sync time. For example, durationMinutes is calculated from start.dateTime and end.dateTime, and isWeekday is derived from dayOfWeek.
hour, dayOfWeek, and isWeekday are derived from the event's start time in UTC, not the event's (or your) local timezone. An event at 9 AM Pacific (UTC-7) has hour 16, and an event late on Sunday evening in a western timezone may already count as Monday. For a rule about a local clock time — "after 16:00 Prague time" — use overlapsTimeOfDay(), which handles daylight saving time for you.
Helper Methods
GateEvent provides helper methods for common checks. hasAttendee and matches perform case-insensitive matching.
| Method | Signature | Returns | Description |
|---|---|---|---|
hasAttendee(pattern) | (pattern: string) => boolean | boolean | Searches attendee email addresses for the given pattern |
matches(pattern) | (pattern: string) => boolean | boolean | Searches the event title and description for the given pattern |
overlapsTimeOfDay(window) | ({ from?, to?, timeZone }) => boolean | boolean | Whether any part of the event falls inside a daily time window |
hasAttendee(pattern)
Performs a case-insensitive search across all attendee email addresses. Returns true if any attendee's email contains the pattern.
// Check for a specific attendee
event.hasAttendee('alice@example.com'); // exact email match
// Check for a domain
event.hasAttendee('@company.com'); // any attendee from company.com
// Case-insensitive
event.hasAttendee('Alice@Example.COM'); // matches "alice@example.com"
matches(pattern)
Performs a case-insensitive search across both the event title and description. Returns true if either field contains the pattern.
// Match by keyword
event.matches('standup'); // matches "Daily Standup", "STANDUP MEETING", etc.
// Matches in title or description
event.matches('confidential'); // true if title OR description contains "confidential"
Prefer matches() over manual event.title/event.description checks — it handles null descriptions and case-insensitivity for you.
overlapsTimeOfDay(window)
Returns true when any part of the event falls inside a daily clock window in the given time zone. Unlike hour, it catches events that merely run into the window: a 14:00 meeting lasting 2.5 hours overlaps "from 16:00".
| Field | Type | Description |
|---|---|---|
from | string | null | 24-hour HH:MM. Omit to start the window at midnight. |
to | string | null | 24-hour HH:MM. Omit to run the window to the end of day. |
timeZone | string | IANA time zone, e.g. "Europe/Prague" or "America/Denver" |
// Block anything still running after 16:00 Prague time
if (event.overlapsTimeOfDay({ from: '16:00', timeZone: 'Europe/Prague' })) {
return { pass: false, reason: 'After hours' };
}
// Overnight: a `to` earlier than `from` wraps past midnight
event.overlapsTimeOfDay({
from: '22:00',
to: '06:00',
timeZone: 'Europe/Prague',
});
- Daylight saving time is handled:
"16:00"inEurope/Praguemeans 16:00 CET in winter and 16:00 CEST in summer. - Touching is not overlapping: an event ending exactly at
from, or starting exactly atto, does not match. - Events spanning midnight are checked against every day they touch.
- All-day events always match. Add
!event.isAllDayto leave them out. - An invalid time or time zone is an error, so the event is blocked and the test runner shows the message.
GateResult Interface
The gate function must return a GateResult object (or a simple boolean). Here is the full interface:
interface GateResult {
pass: boolean;
reason?: string;
transform?: {
title?: string;
description?: string;
location?: string;
visibility?: 'default' | 'public' | 'private';
showAs?: 'free' | 'busy';
type?: 'default' | 'outOfOffice';
autoDecline?: boolean;
reminders?: false | number;
color?:
| 'tomato'
| 'flamingo'
| 'tangerine'
| 'banana'
| 'sage'
| 'basil'
| 'peacock'
| 'blueberry'
| 'lavender'
| 'grape'
| 'graphite';
preBufferMinutes?: number;
postBufferMinutes?: number;
};
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
pass | boolean | Yes | true = sync the event to the target calendar. false = block (skip) the event. |
reason | string | No | Human-readable explanation for the decision. Max 200 characters. Most useful when pass is false -- the reason appears in sync logs. |
transform | object | No | Override event properties on the synced copy. Only applies when pass is true. |
Transform Fields
| Field | Type | Max Length | Description |
|---|---|---|---|
title | string | 500 chars | Replace the event title on the synced copy |
description | string | 5,000 chars | Replace the event description on the synced copy |
location | string | 500 chars | Replace the event location on the synced copy. Set to an empty string to clear it. |
visibility | 'default' | 'public' | 'private' | -- | Change the event visibility |
showAs | 'free' | 'busy' | -- | Change the free/busy status |
preBufferMinutes | integer | 0–60 | Start the synced event earlier by N minutes (pre-event buffer). Ignored for all-day events. |
postBufferMinutes | integer | 0–60 | End the synced event later by N minutes (post-event buffer). Ignored for all-day events. |
type | 'default' | 'outOfOffice' | -- | Set the event kind in the destination calendar. See Out of office below. |
autoDecline | boolean | -- | Out of office only: auto-decline new conflicting invitations. See Out of office below. |
reminders | false | number | 1–40320 | Set reminders on the synced copy. See Reminders below. |
Reminders
transform.reminders controls the alerts on the synced copy. The original event is never touched.
| Value | Result |
|---|---|
| omitted | The destination calendar's own reminder defaults apply |
false | No reminders at all |
| a number | One alert that many minutes before the event starts (1–40320, up to four weeks) |
// Mirror the calendar without being pinged twice
return { pass: true, transform: { reminders: false } };
// An hour's warning on the synced copy
return { pass: true, transform: { reminders: 60 } };
Reminders are never copied from the source event, so a synced copy's alerts come from the rule alone. The setting is re-applied on every create and update — if you change a reminder by hand on a synced copy, the next sync of that event puts it back.
By destination:
- Google, Outlook: both directions apply. Without a rule setting, these calendars add their own default reminder — which is why a mirrored event can alert you even though you never asked it to.
- Apple/iCloud: synced copies carry no alert unless a rule asks for one. Setting a number is the only way to get an alert on an Apple destination;
falseis already how they behave. - Hosted feeds and invitations: not supported. Subscribed-calendar alarms are ignored by Google and Outlook and are a per-subscriber toggle in Apple, so whoever subscribes sets their own alerts.
Outlook events carry a single reminder, so a lead time set by a rule replaces whatever was there. Clearing the lead time from a rule turns reminders back on but leaves the last interval in place on existing copies — set a new number if you need to change it.
| color | colour name | -- | Set the colour of the synced copy. See Event color below. |
Event color
Set transform.color to one of Google's event colour names to colour the synced copy. This overrides the pipe's own default colour for that event.
'tomato', 'flamingo', 'tangerine', 'banana', 'sage', 'basil', 'peacock', 'blueberry', 'lavender', 'grape', 'graphite'
- Google: the matching colour from Google's event palette, on any calendar and any account.
- Google out-of-office events: ignored — Google renders those in a fixed style.
- Outlook: ignored. Outlook has no per-event colour; its categories only show a colour if you have already defined that category on your mailbox.
- Apple/CalDAV, hosted calendars, invitation delivery: ignored.
To colour everything a pipe syncs, set the pipe's colour instead (see Event Colour) — no gate code needed. Use transform.color when the colour should depend on the event.
// Colour interviews differently from everything else this pipe syncs
if (event.matches('interview')) {
return { pass: true, transform: { color: 'tomato' } };
}
return { pass: true };
Out of office
Set transform.type to "outOfOffice" to write the synced event as an Out of office event in the destination calendar.
- Google: writes a real Out of office event — requires the primary calendar of a Google Workspace account.
- Google personal accounts (
@gmail.com), non-primary calendars, or all-day events: fall back to a busy event. - Outlook: shown as "Out of office" on any calendar (no Workspace requirement).
- Apple/CalDAV, hosted calendars: falls back to a busy event.
Cannot be combined with showAs: "free" — out-of-office is always busy.
return { pass: true, transform: { type: 'outOfOffice' } };
Auto-decline new conflicting invitations (opt-in)
Set transform.autoDecline to true together with type: "outOfOffice" to have Google automatically decline new invitations that conflict with the synced Out of office event. Off by default.
- Forward-looking only: from the moment the Out of office block syncs, new conflicting invitations are declined. Events already on the calendar are never touched.
- Only effective where a real Out of office event is written — the primary calendar of a Google Workspace account. Everywhere else (Outlook, Apple/CalDAV, hosted, personal Google accounts, all-day events) the flag is ignored.
- Inviters see Google's standard decline message.
return { pass: true, transform: { type: 'outOfOffice', autoDecline: true } };
Transforms only modify the synced copy on the target calendar. The original event on the source calendar is never changed.
Return Types
The gate function can return any of these types:
Boolean
The simplest return type. true passes the event, false blocks it:
function rule(event: GateEvent): boolean {
return event.isWeekday;
}
GateResult
An object with pass, optional reason, and optional transform:
function rule(event: GateEvent): GateResult {
return {
pass: true,
reason: 'Synced normally',
transform: { title: `[Sync] ${event.title}` },
};
}
GateResult | boolean
The union type allows mixing both styles in the same function:
function rule(event: GateEvent): GateResult | boolean {
if (event.matches('confidential')) {
return { pass: false, reason: 'Blocked confidential event' };
}
return true; // pass everything else
}
Complete Example
This comprehensive gate function demonstrates multiple properties, methods, and transform options:
function rule(event: GateEvent): GateResult {
// Block confidential events entirely
if (event.matches('confidential') || event.matches('secret')) {
return { pass: false, reason: 'Blocked confidential event' };
}
// Block cancelled events
if (event.status === 'cancelled') {
return { pass: false, reason: 'Event is cancelled' };
}
// Redact personal events -- show the time block but hide details
if (event.matches('personal') || event.matches('private')) {
return {
pass: true,
transform: {
title: 'Personal Event',
description: '',
visibility: 'private',
},
};
}
// Mark solo events (no other attendees) as free
if (event.attendeeCount <= 1) {
return {
pass: true,
transform: { showAs: 'free' },
};
}
// Add prefix to large meetings (5+ attendees) for easy identification
if (event.attendeeCount >= 5) {
return {
pass: true,
transform: {
title: `[Team] ${event.title}`,
},
};
}
// Pass everything else unchanged
return { pass: true };
}
This example:
- Uses
matches()for keyword filtering (confidential, personal) - Checks
statusfor cancelled events - Uses
attendeeCountfor attendee-based logic - Applies different transforms based on conditions
- Returns a reason for blocked events
- Falls through to a default pass for unmatched events
Related
- Code Editor — author and test functions with autocompletion.
- Cookbook — recipes that use these properties.
- Templates — common scenarios as one-click presets.