Skip to main content

AppleScript Support

BusyCal includes a command-based AppleScript automation API. You can list accounts and calendars, query events and tasks, create new items, and update or delete existing ones. All write operations route through BusyCal's normal edit pipeline, so changes stay in sync with the UI and your calendar accounts.

note

BusyCal's AppleScript support is data-only — you can read and write calendar data, but you cannot control the UI (such as switching views, selecting dates, or opening windows) via AppleScript.

Getting Started

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

Commands are called on the BusyCal application object using tell application "BusyCal". Each command uses native AppleScript syntax with labeled parameters and returns a record or list of records.

tell application "BusyCal"
set results to query events given |search text|:"review", |start date|:(current date), |end date|:((current date) + (7 * days)), mode:"uiConsistent", |fetch limit|:20
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

Multi-word parameter labels must be wrapped in pipes (|...|). For example, write |search text|, |start date|, and |fetch limit| — not searchText or search_text. Single-word labels like mode, scope, and title do not need pipes.

Command Summary

Read commands:

  • listAccounts (AppleScript: list accounts) - list calendar accounts
  • listCalendars (AppleScript: list calendars) - list calendars, optionally filtered by account
  • queryEvents (AppleScript: query events) - search and filter events
  • queryTasks (AppleScript: query tasks) - search and filter tasks

Write commands:

  • createEvent (AppleScript: create event) - create a new event
  • createTask (AppleScript: create task) - create a new task
  • moveEvent (AppleScript: move event) - reschedule an event
  • updateEvent (AppleScript: update event) - change an event's title, location, or notes
  • updateTask (AppleScript: update task) - change a task's title or notes
  • deferTask (AppleScript: defer task) - change a task's due date
  • completeTask (AppleScript: complete task) - mark a task as complete
  • uncompleteTask (AppleScript: uncomplete task) - mark a completed task as not completed
  • deleteEvent (AppleScript: delete event) - delete an event
  • deleteTask (AppleScript: delete task) - delete a task

Identity Fields

Query and create commands return records that include stable identity fields:

FieldDescription
idOccurrence-level identifier. Use this value for all update, move, defer, complete, uncomplete, and delete commands.
seriesUIDSeries identifier shared 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

listAccounts

Returns a list of account records.

ArgumentTypeRequiredDescription
includeDisabledBooleanNoInclude disabled accounts. Default: false.

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

listCalendars

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

ArgumentTypeRequiredDescription
accountIDStringNoLimit results to calendars belonging to this account.

Returned fields: id, calendarUID, accountID, title, isSubscribed, supportsEvents, supportsTasks

Querying Events and Tasks

queryEvents

Returns a list of event records matching the given criteria.

ArgumentTypeRequiredDescription
searchTextStringNoFull-text search query.
calendarIDStringNoLimit results to a specific calendar.
startDateDateNoStart of the date range.
endDateDateNoEnd of the date range.
modeStringNoQuery mode: "uiConsistent" (default) or "rawExhaustive".
fetchLimitNumberNoMaximum number of results. 0 for unlimited.

Returned fields: id, seriesUID, title, calendarID, calendarUID, occurrenceDate, managedObjectURI, startDate, endDate, location

queryTasks

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

Returned fields: id, seriesUID, title, calendarID, calendarUID, occurrenceDate, managedObjectURI, dueDate, isCompleted

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.

Creating Events and Tasks

createEvent

Creates a new event and returns the created event record.

ArgumentTypeRequiredDescription
titleStringYesEvent title.
startDateDateYesEvent start date and time.
endDateDateNoEvent end date and time. If omitted, BusyCal uses a default duration.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
allDayBooleanNoSet to true for an all-day event.
notesStringNoEvent notes.

createTask

Creates a new task and returns the created task record.

ArgumentTypeRequiredDescription
titleStringYesTask title.
dueDateDateNoDue date and time. If omitted, creates an undated task.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
notesStringNoTask notes.

Updating and Moving

moveEvent

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

ArgumentTypeRequiredDescription
idStringYesThe event's id from a query or create result.
scopeStringNoRecurrence scope. Default: "all".
startDateDateYesNew start date and time.

updateEvent

Updates an event's text fields. Returns the updated event record. You must provide at least one of title, location, or notes.

ArgumentTypeRequiredDescription
idStringYesThe 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.

updateTask

Updates a task's text fields. Returns the updated task record. You must provide at least one of title or notes.

ArgumentTypeRequiredDescription
idStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
titleStringNoNew title.
notesStringNoNew notes. Pass an empty string to clear it.

deferTask

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

ArgumentTypeRequiredDescription
idStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
dueDateDateNoNew due date. Omit to make the task undated.

completeTask

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

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

uncompleteTask

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

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

Deleting

deleteEvent

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

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

deleteTask

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

ArgumentTypeRequiredDescription
idStringYesThe 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 |account id|: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 |search text|:"review", |start date|:today, |end date|:weekOut, mode:"uiConsistent", |fetch limit|: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 |search text|:"follow up", |start date|:today, |end date|:twoWeeksOut, mode:"uiConsistent", |fetch limit|: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", |start date|:startDateValue, |end date|:endDateValue, |all day|:false, notes:"Weekly sync"

-- Move it two days later (duration is preserved automatically)
set movedEvent to move event given |id|:(|id| of createdEvent), scope:"all", |start date|: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", |start date|:startDateValue, |end date|:endDateValue

-- Update the title and add a location
set updatedEvent to update event given |id|:(|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", |due date|:dueDateValue

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

-- Mark as not completed
set uncompletedTask to uncomplete task given |id|:(|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", |due date|:dueDateValue, notes:"Draft for v2.5"

-- Update the notes
set updatedTask to update task given |id|:(|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 |id|:(|id| of updatedTask), scope:"all", |due date|:(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 |id| 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", |start date|:startDateValue, |calendar id|: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", |start date|:startDateValue, |end date|:endDateValue

-- Delete the event (all occurrences)
set deletedRecord to delete event given |id|:(|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", |due date|:dueDateValue

-- Delete the task
set deletedRecord to delete task given |id|:(|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 |search text|:"Daily Standup", |start date|:today, |end date|: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 |id|:(|id| of tomorrow), scope:"one", |start date|:(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 |id|:(|id| of thirdOccurrence), scope:"future", title:"Morning Standup"
log "Renamed future occurrences: " & (title of updatedEvent)
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 |search text|:"TBD", |start date|:today, |end date|:monthOut

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

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

Error Handling

Commands signal errors as standard AppleScript errors (error number -10000). Common error conditions 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:"", |start date|:(current date)
on error errMsg
log "Error: " & errMsg
end try
end tell

Tips

  • Always use the id returned from query or create commands when calling update, move, or delete commands. Do not construct IDs manually.
  • Because id and isCompleted are AppleScript reserved words, wrap them in pipes when reading from a record: |id| of someRecord, |isCompleted| of someRecord.
  • Multi-word parameter labels must also be wrapped in pipes: |start date|, |search text|, |calendar id|, etc.
  • For recurring items, set scope explicitly to control which occurrences are affected. If you omit it, "all" is used (the entire series).
  • To discover all items including hidden or unchecked calendars, use mode:"rawExhaustive" in your queries. The default "uiConsistent" mode only returns items from calendars that are currently visible and checked in BusyCal.
  • Before creating an event or task on a specific calendar, check that the calendar supports the item type using the supportsEvents or supportsTasks field from list calendars.
  • Every create and update command returns the resulting record, so you can chain operations by reading the id from each result.