Skip to main content

AppleScript Support

BusyCal includes an AppleScript automation API. You can list accounts and calendars, search events and tasks by date or keyword, check free/busy availability, and create, update, or delete items — all from a script. Changes made through the API stay in sync with the BusyCal UI and your calendar accounts.

note

BusyCal's AppleScript support is primarily data-oriented — you can read and write calendar data from scripts. The one exception is open item, which reveals a specific event or task in the BusyCal window.

Requires BusyCal version 2026.1.3 or later.

tip

If you use Claude Desktop, you can also control BusyCal through natural language using the BusyCal MCP extension. It uses the same automation engine under the hood.

Getting Started

Open Script Editor (in Applications > Utilities) and paste any of the examples below to try them out.

All commands are called inside a tell application "BusyCal" block. Each command uses labeled parameters and returns a record or list of records.

tell application "BusyCal"
set results to query events given searchText:"review", startDate:(current date), endDate:((current date) + (7 * days)), mode:"uiConsistent", fetchLimit:20
end tell

You can also search across multiple item types at once with query items:

tell application "BusyCal"
set rangeStart to current date
set rangeEnd to rangeStart + (3 * days)
set results to query items given searchText:"review", startDate:rangeStart, endDate:rangeEnd, itemTypes:"event, task, journal", searchFields:"title", mode:"uiConsistent", fetchLimit:25
end tell

To check free/busy availability within your working hours:

tell application "BusyCal"
set dayStart to current date
set freeBusyDays to query availability given startDate:dayStart, endDate:(dayStart + (2 * days)), kind:"both", minimumDurationMinutes:30, respectWorkingHours:true
end tell

To find your next 60-minute free slot:

tell application "BusyCal"
set nextSlot to find next available given minimumDurationMinutes:60, respectWorkingHours:true
end tell

You can also run scripts from Terminal using osascript:

osascript -e 'tell application "BusyCal" to list accounts'

For multi-line scripts, save the script to a file (e.g. myscript.applescript) and run:

osascript myscript.applescript
Parameter labels

Parameter names are flexible — startDate, start date, and start_date all work. This guide uses camelCase (e.g. startDate) for consistency.

Command Summary

Utility commands:

  • get URL — open a BusyCal URL command

Read commands:

Navigation:

  • open item — reveal an event or task in the BusyCal window

Reference:

Write commands:

Utility

get URL

Opens a busycal://... URL, just like clicking a BusyCal link.

ArgumentTypeRequiredDescription
Direct parameterStringYesThe URL to open, for example busycal://new or other supported BusyCal URL actions.

Identity Fields

Query and create commands return records with the following identity fields:

FieldDescription
idUnique identifier for this occurrence. Use this value as itemID in update, move, defer, complete, uncomplete, and delete commands.
seriesUIDShared identifier across all occurrences of a recurring item.
occurrenceDateThe date of this particular occurrence.
managedObjectURIInternal object reference.
note

Because id and isCompleted are AppleScript reserved words, wrap them in pipes when reading from a record: |id| of someRecord, |isCompleted| of someRecord.

Accounts and Calendars

list accounts

Returns a list of account records.

By default, BusyCal returns only accounts that are currently enabled. Set includeDisabled:true if you also want disabled accounts.

ArgumentTypeRequiredDescription
includeDisabledBooleanNoInclude disabled accounts. Default: false.

Returned fields: id, title, serviceName, serviceType, isEnabled, isPendingAddition

list calendars

Returns a list of calendar records, optionally filtered to a single account.

By default, BusyCal only returns calendars whose owning account is currently enabled. Set includeDisabledAccounts:true if you also want calendars from disabled accounts.

ArgumentTypeRequiredDescription
accountIDStringNoLimit results to calendars belonging to this account.
includeDisabledAccountsBooleanNoInclude calendars from disabled accounts. Default: false.
includeWebDAVBooleanNoInclude WebDAV subscription calendars. Default: true.
includeContactCalendarsBooleanNoInclude birthday and anniversary calendars. Default: true.
includeHolidayCalendarsBooleanNoInclude holiday calendars. Default: true.

Returned fields: calendarID, accountID, title, isSubscribed, supportsEvents, supportsTasks

Querying Items

query items

Searches across events, tasks, journals, graphics, and stickies in a single call.

ArgumentTypeRequiredDescription
searchTextStringNoText to search for. Omit or pass an empty string to list items by date only.
searchFieldsStringNoComma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields.
calendarIDStringNoLimit results to a specific calendar. Use the calendarID value from list calendars.
startDateDate or StringNoStart of date range. Returns items on or after this date. Accepts AppleScript dates or strings like "2026-03-14", "2026-03-14 15:30", or "2026-03-14T15:30:00".
endDateDate or StringNoEnd of date range. If startDate is omitted, BusyCal uses the current time as the start. Day-only strings like "2026-03-14" include the full day.
itemTypesStringNoComma-separated types to include: "event", "task", "journal", "graphic", "sticky". When omitted, all types are returned.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.
fetchLimitNumberNoMaximum number of results. 0 for unlimited. Queries without searchText are capped to 100 results by default.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, dueDate, location, alarms, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier, dueTimeZoneIdentifier

The type field tells you what kind of item each record is (e.g. "event", "task", "journal"). Fields like dueDate, startDate, endDate, location, and isCompleted are only present when they apply to that item type. isFloating tells you whether BusyCal currently treats the item as floating time. For fixed-time items, BusyCal returns per-field timezone identifiers — see Timezone Fields.

query events

Returns a list of event records matching the given criteria.

ArgumentTypeRequiredDescription
searchTextStringNoText to search for. Omit or pass an empty string to list events by date only.
searchFieldsStringNoComma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields.
calendarIDStringNoLimit results to a specific calendar.
startDateDate or StringNoStart of date range. Returns events on or after this date. Accepts AppleScript dates or strings like "2026-03-14".
endDateDate or StringNoEnd of date range. If startDate is omitted, BusyCal uses the current time as the start. Day-only strings include the full day.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.
fetchLimitNumberNoMaximum number of results. 0 for unlimited. Queries without searchText are capped to 100 results by default.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, location, alarms, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

query tasks

Returns a list of task records matching the given criteria. Takes the same arguments as query events.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, dueDate, alarms, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

Availability

These commands return free/busy time slots instead of individual items. They accept the same date formats as the query commands.

When respectWorkingHours is true (the default), results are limited to:

  • your configured working days
  • your Day starts at and Day ends at times (set in BusyCal > Settings > General)

When calendarIDs is omitted, BusyCal checks availability across all accounts, using:

  • event-capable calendars only
  • holiday and WebDAV subscription calendars are excluded

If you pass specific calendarIDs, BusyCal uses exactly those calendars, even if they are holiday or subscription calendars.

query availability

Returns one record per day showing free and/or busy time slots.

ArgumentTypeRequiredDescription
startDateDate or StringNoStart of the range. Defaults to today. Accepts AppleScript dates or strings like "2026-03-14" or "2026-03-14T15:30:00".
endDateDate or StringNoEnd of the range. Defaults to the same day as startDate. Day-only strings like "2026-03-14" include the full day.
calendarIDsStringNoComma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope.
kindStringNoWhat to return: "free" (default), "busy", or "both".
minimumDurationMinutesNumberNoOnly return free slots at least this many minutes long. Does not filter busy slots.
respectWorkingHoursBooleanNoWhen true (default), limits results to your configured working days and hours.
busyTypesStringNoComma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.

Each day record contains:

FieldDescription
dateThe start of this day. Interpret in the returned timeZoneIdentifier.
timeZoneIdentifierBusyCal's active timezone identifier. Use this to interpret all dates in the record.
windowStartDateStart of the working/available window for this day.
windowEndDateEnd of the working/available window for this day.
isWorkingDaytrue if this day is a configured working day.
busySpansList of busy time slots (each with startDate, endDate, durationMinutes, timeZoneIdentifier).
freeSpansList of free time slots (each with startDate, endDate, durationMinutes, timeZoneIdentifier).

Example:

tell application "BusyCal"
set freeBusyDays to query availability given startDate:"2026-03-17", endDate:"2026-03-19", kind:"both", minimumDurationMinutes:30, respectWorkingHours:true
end tell

find next available

Finds the first free time slot that is long enough, or returns missing value if none exists in the range.

ArgumentTypeRequiredDescription
startDateDate or StringNoStart searching from this time. Defaults to now. Accepts the same date formats as query availability.
endDateDate or StringNoStop searching at this time. Defaults to 7 days after startDate. Day-only strings like "2026-03-14" include the full day.
calendarIDsStringNoComma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope.
minimumDurationMinutesNumberNoMinimum length of the free slot in minutes. Default: 30.
respectWorkingHoursBooleanNoWhen true (default), only looks within your configured working days and hours.
busyTypesStringNoComma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.

Returned fields: startDate, endDate, durationMinutes, timeZoneIdentifier

Example:

tell application "BusyCal"
set nextSlot to find next available given startDate:"2026-03-17 09:00", endDate:"2026-03-20 18:00", minimumDurationMinutes:60, respectWorkingHours:true
end tell

Query Modes

ModeBehavior
uiConsistentReturns items matching what you see in BusyCal - respects checked/unchecked calendars and visibility settings. This is the default.
rawExhaustiveReturns all items regardless of visibility, including unchecked calendars, disabled accounts, hidden events, declined meetings, and canceled events.

Date Filtering

All query and availability commands support flexible date ranges:

  • Start date only — returns items on or after that date. For find next available, searches from that moment through its default 7-day window.
  • End date only — BusyCal uses the current time as the start.
  • Both start and end — returns only items within that window.

You can pass native AppleScript dates or date strings in these formats: "2026-03-14", "2026-03-14 15:30", "2026-03-14T15:30:00", "2026-03-14 15:30:00", "2026-03-14T15:30:00-05:00", "2026-03-14T15:30:00Z", or "2026-03-14 15:30:00 +0400". A day-only endDate like "2026-03-14" includes the entire day.

For create, move, and defer commands, BusyCal interprets date strings this way:

  • Strings without a time zone (for example "2026-03-17T14:00:00") are treated as local wall-clock times in BusyCal's active app time zone. If you have overridden the app's time zone in BusyCal, that override is used. Otherwise BusyCal uses your current local time zone.
  • Strings with Z or an explicit offset (for example "2026-03-17T14:00:00+04:00" or "2026-03-17 14:00:00 +0400") are treated as fixed-time timestamps. BusyCal preserves that absolute instant instead of reinterpreting it as floating time.
  • Native AppleScript date values also preserve the exact instant AppleScript resolved before handing it to BusyCal.
  • For a single create event call, keep startDate and endDate in the same style. In other words, don't mix a native AppleScript date or explicit-offset timestamp with an unzoned local-time string in the same request.
  • Returned event and task records include isFloating and, for fixed-time items, per-field timezone identifiers so your script can tell which behavior BusyCal applied. See Timezone Fields for details.

Relative Alarms

BusyCal's automation commands support relative reminders via the alarms parameter:

  • Create commands (create event, create task):
    • omit alarms to keep BusyCal's normal default reminders
    • pass a list such as {-60, -15} to create exactly those reminders instead
  • Update commands (update event, update task):
    • omit alarms to leave reminders unchanged
    • pass {} to clear all reminders
    • pass a non-empty list such as {-30} to add those reminder offsets if they are not already present

Values are signed relative minutes:

  • -15 — 15 minutes before
  • -1440 — 1 day before

BusyCal continues to respect the capabilities of the underlying account. If a service cannot store a requested reminder remotely, BusyCal automatically falls back to a local reminder instead.

Timezone Fields

Returned records include timezone metadata so your script can tell whether BusyCal stored an item as floating time or as a fixed-time instant with a specific timezone.

FieldPresent onDescription
isFloatingAll itemstrue when BusyCal treats the item as floating (local wall-clock) time.
occurrenceTimeZoneIdentifierAll itemsTimezone for the occurrence date, when stored.
startTimeZoneIdentifierEventsTimezone for the start date, when stored.
endTimeZoneIdentifierEventsTimezone for the end date, when stored.
dueTimeZoneIdentifierTasksTimezone for the due date, when stored.
timeZoneIdentifierAll itemsCompatibility alias — older scripts may use this. New scripts should use the per-field identifiers above.

An event whose start and end are in different timezones (for example a flight departing in one timezone and arriving in another) will return different values for startTimeZoneIdentifier and endTimeZoneIdentifier. UTC timestamps return "GMT" as the timezone identifier.

Creating Events and Tasks

create natural language item

Creates an event, task, or journal by passing natural-language text through BusyCal's quick-entry parser — the same parser used by the Quick Entry window in BusyCal. Returns the created item record.

You can call this command using either AppleScript name:

  • create natural language item
  • quick add item
ArgumentTypeRequiredDescription
textStringYesNatural-language quick-entry text, such as "Lunch with John tomorrow at 2pm" or "Finish report Friday".
itemTypeStringYesThe type of item to create: "event", "task", or "journal".
calendarIDStringNoTarget calendar. If omitted, BusyCal uses its default calendar for the requested item type.
notesStringNoNotes to attach to the item after parsing.

Returned fields: Same as the item's query result — id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date, alarm, and timezone fields appropriate to the item type.

create event

Creates a new event and returns the created event record.

ArgumentTypeRequiredDescription
titleStringYesEvent title.
startDateDate or StringYesEvent start date and time. A date-only value like "2026-04-14" creates an all-day event. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset.
endDateDate or StringNoEvent end date and time. If omitted, BusyCal uses a default duration. For all-day events, a date-only value is the inclusive final day — so startDate:"2026-04-14" with endDate:"2026-04-19" runs through April 19. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
allDayBooleanNoSet to true for an all-day event. For all-day events, prefer date-only startDate/endDate values.
locationStringNoEvent location, such as "Conference Room B" or "1 Apple Park Way, Cupertino, CA 95014".
alarmsList of NumbersNoRelative reminder minutes, such as {-60, -15}. Only include when reminders were explicitly requested. Omit to keep BusyCal's default reminders.
notesStringNoEvent notes.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, alarms, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

create task

Creates a new task and returns the created task record.

ArgumentTypeRequiredDescription
titleStringYesTask title.
dueDateDate or StringNoDue date and time. If omitted, creates an undated task. A date-only value like "2026-04-14" creates an all-day task due that day. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
alarmsList of NumbersNoRelative reminder minutes, such as {-1440, -60}. Only include when reminders were explicitly requested. Omit to keep BusyCal's default reminders. Undated tasks cannot accept explicit alarms.
notesStringNoTask notes.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, alarms, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

open item

Reveals an event or task in the BusyCal window, selecting it and opening its Info Panel. Returns the revealed item record.

ArgumentTypeRequiredDescription
itemIDStringYesThe item's id from a query or create result.

Returned fields: Same as the item's query result (id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date and timezone fields appropriate to the item type).

Updating and Moving

move event

Reschedules an event to a new date and time. The event's original duration is preserved. Returns the updated event record.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id from a query or create result.
scopeStringNoRecurrence scope. Default: "all".
startDateDate or StringYesNew start date and time. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

update event

Updates an event's title, location, notes, or reminders. Returns the updated event record. You must provide at least one field to change.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id.
scopeStringNoRecurrence scope. Default: "all".
titleStringNoNew title.
locationStringNoNew location. Pass an empty string to clear it.
notesStringNoNew notes. Pass an empty string to clear it.
alarmsList of NumbersNoRelative reminder minutes. Pass {} to clear all reminders, omit to leave them unchanged, or pass a non-empty list to add those reminder offsets.

update task

Updates a task's title, notes, or reminders. Returns the updated task record. You must provide at least one field to change.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
titleStringNoNew title.
notesStringNoNew notes. Pass an empty string to clear it.
alarmsList of NumbersNoRelative reminder minutes. Pass {} to clear all reminders, omit to leave them unchanged, or pass a non-empty list to add those reminder offsets.

defer task

Changes a task's due date. Returns the updated task record.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
dueDateDate or StringNoNew due date. Omit to make the task undated. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

complete task

Marks a task as complete. Returns the updated task record.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
completedAtDateNoCompletion timestamp. If omitted, uses the current time.

uncomplete task

Marks a completed task as not completed. Returns the updated task record.

You can call this command using either AppleScript name:

  • uncomplete task
  • reopen task
ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".

Deleting

delete event

Deletes an event. Returns a record with the deleted item's id and deleted:true.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id.
scopeStringNoRecurrence scope. Default: "all".

delete task

Deletes a task. Returns a record with the deleted item's id and deleted:true.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".

Recurrence Scope

Commands that modify or delete items accept a scope argument to control which occurrences are affected:

ScopeMeaning
"one"This occurrence only.
"future"This and all future occurrences.
"all"All occurrences (the entire series). This is the default.

For non-recurring items, the scope is ignored - the single item is always affected.

Examples

List accounts and calendars

tell application "BusyCal"
-- Get all enabled accounts
set accounts to list accounts

repeat with accountRecord in accounts
set accountID to |id| of accountRecord
set accountTitle to title of accountRecord

-- Get calendars for this account
set accountCalendars to list calendars given accountID:accountID

log "Account: " & accountTitle & " (" & (count of accountCalendars) & " calendars)"

repeat with cal in accountCalendars
log " - " & title of cal & " (events: " & supportsEvents of cal & ", tasks: " & supportsTasks of cal & ")"
end repeat
end repeat
end tell

Query events for the next 7 days

tell application "BusyCal"
set today to current date
set weekOut to today + (7 * days)

-- Find events containing "review" in the next week
set matches to query events given searchText:"review", startDate:today, endDate:weekOut, mode:"uiConsistent", fetchLimit:50

repeat with evt in matches
log (title of evt) & " - " & (startDate of evt) & " to " & (endDate of evt)
end repeat
end tell

Query tasks for the next 14 days

tell application "BusyCal"
set today to current date
set twoWeeksOut to today + (14 * days)

-- Find tasks containing "follow up" in the next two weeks
set matches to query tasks given searchText:"follow up", startDate:today, endDate:twoWeeksOut, mode:"uiConsistent", fetchLimit:50

repeat with t in matches
set status to "open"
if |isCompleted| of t then set status to "done"
log (title of t) & " - due: " & (dueDate of t) & " [" & status & "]"
end repeat
end tell

Create and move an event

-- Set up a start time: tomorrow at 10:00 AM
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 10
set minutes of startDateValue to 0
set seconds of startDateValue to 0

-- One hour duration
set endDateValue to startDateValue + (60 * minutes)

-- Where we want to move it: two days later
set movedStart to startDateValue + (2 * days)

tell application "BusyCal"
-- Create the event
set createdEvent to create event given title:"Team Standup", startDate:startDateValue, endDate:endDateValue, allDay:false, location:"Conference Room A", notes:"Weekly sync"

-- Move it two days later (duration is preserved automatically)
set movedEvent to move event given itemID:(|id| of createdEvent), scope:"all", startDate:movedStart

log "Event moved to: " & (startDate of movedEvent)
end tell

Update an event

-- Create an event, then update its title and location
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 14
set minutes of startDateValue to 0
set seconds of startDateValue to 0
set endDateValue to startDateValue + (45 * minutes)

tell application "BusyCal"
set createdEvent to create event given title:"Planning Meeting", startDate:startDateValue, endDate:endDateValue

-- Update the title and add a location
set updatedEvent to update event given itemID:(|id| of createdEvent), scope:"all", title:"Q2 Planning Meeting", location:"Conference Room B"

log "Updated: " & (title of updatedEvent) & " at " & (location of updatedEvent)
end tell

Create, complete, and uncomplete a task

-- Set a due date: two days from now at 5:00 PM
set dueDateValue to current date
set dueDateValue to dueDateValue + (2 * days)
set hours of dueDateValue to 17
set minutes of dueDateValue to 0
set seconds of dueDateValue to 0

tell application "BusyCal"
-- Create the task
set createdTask to create task given title:"Review pull requests", dueDate:dueDateValue

-- Mark it complete
set completedTask to complete task given itemID:(|id| of createdTask), scope:"all", completedAt:(current date)
log "Completed: " & |isCompleted| of completedTask -- true

-- Mark as not completed
set uncompletedTask to uncomplete task given itemID:(|id| of completedTask), scope:"all"
log "Uncompleted: " & |isCompleted| of uncompletedTask -- false
end tell

Update and defer a task

-- Due in 3 days
set dueDateValue to current date
set dueDateValue to dueDateValue + (3 * days)

tell application "BusyCal"
-- Create a task with notes
set createdTask to create task given title:"Write release notes", dueDate:dueDateValue, notes:"Draft for v2.5"

-- Update the notes
set updatedTask to update task given itemID:(|id| of createdTask), scope:"all", notes:"Draft for v2.5 - include migration steps"

-- Push the due date back by 2 more days
set deferredTask to defer task given itemID:(|id| of updatedTask), scope:"all", dueDate:(dueDateValue + (2 * days))

log "New due date: " & (dueDate of deferredTask)
end tell

Create an event on a specific calendar

tell application "BusyCal"
-- Find the "Work" calendar
set allCalendars to list calendars
set workCalID to missing value

repeat with cal in allCalendars
if title of cal is "Work" and supportsEvents of cal then
set workCalID to calendarID of cal
exit repeat
end if
end repeat

if workCalID is missing value then
log "No 'Work' calendar found."
return
end if

-- Create an event on the Work calendar
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 9
set minutes of startDateValue to 0
set seconds of startDateValue to 0

set createdEvent to create event given title:"Client call", startDate:startDateValue, calendarID:workCalID
log "Created on calendar: " & (calendarID of createdEvent)
end tell

Delete an event

set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 16
set minutes of startDateValue to 0
set seconds of startDateValue to 0
set endDateValue to startDateValue + (30 * minutes)

tell application "BusyCal"
set createdEvent to create event given title:"Temporary event", startDate:startDateValue, endDate:endDateValue

-- Delete the event (all occurrences)
set deletedRecord to delete event given itemID:(|id| of createdEvent), scope:"all"
log "Deleted event id: " & |id| of deletedRecord
end tell

Delete a task

set dueDateValue to current date
set dueDateValue to dueDateValue + (1 * days)

tell application "BusyCal"
set createdTask to create task given title:"Temporary task", dueDate:dueDateValue

-- Delete the task
set deletedRecord to delete task given itemID:(|id| of createdTask), scope:"all"
log "Deleted task id: " & |id| of deletedRecord
end tell

Work with recurring events

When you query recurring events, each occurrence is returned as a separate record with its own id. Use the scope parameter to control whether a change applies to one occurrence, this and future occurrences, or the entire series.

tell application "BusyCal"
set today to current date
set twoWeeksOut to today + (14 * days)

-- Find all occurrences of "Daily Standup" in the next two weeks
set standups to query events given searchText:"Daily Standup", startDate:today, endDate:twoWeeksOut

-- Move only tomorrow's occurrence to 30 minutes later
if (count of standups) > 1 then
set tomorrow to item 2 of standups
set originalStart to startDate of tomorrow
set movedEvent to move event given itemID:(|id| of tomorrow), scope:"one", startDate:(originalStart + (30 * minutes))
log "Moved one occurrence to: " & (startDate of movedEvent)
end if

-- Update the title for all future occurrences starting from the third one
if (count of standups) > 2 then
set thirdOccurrence to item 3 of standups
set updatedEvent to update event given itemID:(|id| of thirdOccurrence), scope:"future", title:"Morning Standup"
log "Renamed future occurrences: " & (title of updatedEvent)
end if
end tell

Quick add with natural language

tell application "BusyCal"
-- Create an event using natural language
set newEvent to create natural language item given text:"Lunch with Sarah tomorrow at 1pm", itemType:"event"
log "Created event: " & (title of newEvent) & " at " & (startDate of newEvent)

-- Create a task using natural language on a specific calendar
set allCalendars to list calendars
set workCalID to missing value
repeat with cal in allCalendars
if title of cal is "Work" and supportsTasks of cal then
set workCalID to calendarID of cal
exit repeat
end if
end repeat

if workCalID is not missing value then
set newTask to quick add item given text:"Finish expense report Friday", itemType:"task", calendarID:workCalID, notes:"Q1 expenses"
log "Created task: " & (title of newTask)
end if
end tell

Open an item in BusyCal

tell application "BusyCal"
-- Find the next event containing "standup"
set matches to query events given searchText:"standup", startDate:(current date), fetchLimit:1

if (count of matches) > 0 then
set firstMatch to item 1 of matches
set revealed to open item given itemID:(|id| of firstMatch)
log "Revealed: " & (title of revealed)
end if
end tell

Find events by keyword and update them

tell application "BusyCal"
set today to current date
set monthOut to today + (30 * days)

-- Find all events with "TBD" in the title
set matches to query events given searchText:"TBD", startDate:today, endDate:monthOut

repeat with evt in matches
-- Add a location to each one
update event given itemID:(|id| of evt), scope:"all", location:"Room 101"
end repeat

log "Updated " & (count of matches) & " events"
end tell

Query mixed item types

tell application "BusyCal"
set rangeStart to current date
set rangeEnd to rangeStart + (3 * days)
set itemsFound to query items given searchText:"review", startDate:rangeStart, endDate:rangeEnd, itemTypes:"event, task, journal", searchFields:"title", mode:"uiConsistent", fetchLimit:25

repeat with itemRecord in itemsFound
set itemType to type of itemRecord
set itemTitle to title of itemRecord
log "- " & itemType & ": " & itemTitle
end repeat
end tell

Check availability for the next 3 days

tell application "BusyCal"
set today to current date
set threeDaysOut to today + (3 * days)

-- Get both free and busy slots, at least 30 minutes long, within working hours
set days to query availability given startDate:today, endDate:threeDaysOut, kind:"both", minimumDurationMinutes:30, respectWorkingHours:true

repeat with dayRecord in days
log "Date: " & (date of dayRecord) & " (working day: " & (isWorkingDay of dayRecord) & ")"

repeat with freeSlot in (freeSpans of dayRecord)
log " Free: " & (startDate of freeSlot) & " to " & (endDate of freeSlot) & " (" & (durationMinutes of freeSlot) & " min)"
end repeat

repeat with busySlot in (busySpans of dayRecord)
log " Busy: " & (startDate of busySlot) & " to " & (endDate of busySlot) & " (" & (durationMinutes of busySlot) & " min)"
end repeat
end repeat
end tell

Find the next free slot

tell application "BusyCal"
-- Find the next 60-minute free slot within the next 5 working days
set today to current date
set fiveDaysOut to today + (5 * days)

set nextSlot to find next available given startDate:today, endDate:fiveDaysOut, minimumDurationMinutes:60, respectWorkingHours:true

if nextSlot is missing value then
log "No 60-minute slot available in the next 5 days."
else
log "Next free slot: " & (startDate of nextSlot) & " to " & (endDate of nextSlot) & " (" & (durationMinutes of nextSlot) & " min)"
end if
end tell

Create an event with explicit timezones

When booking a flight or scheduling across timezones, you can pass ISO 8601 strings with explicit offsets. BusyCal preserves each timezone separately.

tell application "BusyCal"
-- Flight departing Los Angeles (Pacific) at 9 AM, arriving New York (Eastern) at 5 PM
set flight to create event given title:"Delta flight to New York", startDate:"2026-04-10T09:00:00-07:00", endDate:"2026-04-10T17:00:00-04:00", notes:"Seat 14A"

-- Check the stored timezones
log "Start TZ: " & (startTimeZoneIdentifier of flight)
log "End TZ: " & (endTimeZoneIdentifier of flight)
log "Floating: " & (isFloating of flight)
end tell

Date range filtering

tell application "BusyCal"
-- Start date only: all events from now onward
set eventsAfterNow to query events given startDate:(current date), fetchLimit:20

-- End date only: tasks between now and 7 days out
set tasksThroughWeek to query tasks given endDate:((current date) + (7 * days)), fetchLimit:20

-- Both start and end: events in a 3-day window
set windowStart to current date
set windowEnd to windowStart + (3 * days)
set windowEvents to query events given startDate:windowStart, endDate:windowEnd, fetchLimit:20

-- You can also pass dates as strings
set isoResults to query items given searchText:"launch", startDate:"2026-03-14T09:00:00", endDate:"2026-03-14T18:00:00", itemTypes:"event, journal", fetchLimit:20
end tell

Error Handling

Commands report errors as standard AppleScript errors (error number -10000). Common errors include:

  • Invalid arguments — a required field is missing, the title is empty, endDate is before startDate when creating an event, or move event is called with an endDate argument.
  • Item not found — the id you passed does not match any existing item (it may have been deleted).
  • Edit rejected — the target calendar is read-only, the calendar doesn't support the item type, or BusyCal could not complete the change.
  • Not ready — BusyCal is still opening its database. Wait a moment and try again.

You can catch these with a standard try block:

tell application "BusyCal"
try
set result to create event given title:"", startDate:(current date)
on error errMsg
log "Error: " & errMsg
end try
end tell

Tips

  • Always use the id returned from query or create commands as the itemID for update, move, and delete commands. Don't construct IDs yourself.
  • id and isCompleted are AppleScript reserved words — wrap them in pipes when reading from a record: |id| of someRecord, |isCompleted| of someRecord.
  • Use the argument labels exactly as documented (e.g. startDate, searchText, calendarID, itemID).
  • Get the calendarID value from list calendars — use that when creating items on a specific calendar or filtering queries.
  • For recurring items, set scope to control which occurrences are affected. If you omit it, "all" is used (the entire series).
  • Use mode:"rawExhaustive" to include items from unchecked calendars, disabled accounts, and hidden events. The default "uiConsistent" mode only returns what's visible in BusyCal. See Query Modes.
  • Before creating an event or task on a specific calendar, check supportsEvents or supportsTasks from list calendars.
  • Every create and update command returns the resulting record, so you can chain operations by reading the id from each result.
  • Queries without searchText are capped to 100 results by default. Pass fetchLimit:0 if you want unlimited results.
  • Use searchFields to limit searches to specific fields like "title" or "notes" when you need more targeted matching.