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.
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
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 accountslistCalendars(AppleScript:list calendars) - list calendars, optionally filtered by accountqueryEvents(AppleScript:query events) - search and filter eventsqueryTasks(AppleScript:query tasks) - search and filter tasks
Write commands:
createEvent(AppleScript:create event) - create a new eventcreateTask(AppleScript:create task) - create a new taskmoveEvent(AppleScript:move event) - reschedule an eventupdateEvent(AppleScript:update event) - change an event's title, location, or notesupdateTask(AppleScript:update task) - change a task's title or notesdeferTask(AppleScript:defer task) - change a task's due datecompleteTask(AppleScript:complete task) - mark a task as completeuncompleteTask(AppleScript:uncomplete task) - mark a completed task as not completeddeleteEvent(AppleScript:delete event) - delete an eventdeleteTask(AppleScript:delete task) - delete a task
Identity Fields
Query and create commands return records that include stable identity fields:
| Field | Description |
|---|---|
id | Occurrence-level identifier. Use this value for all update, move, defer, complete, uncomplete, and delete commands. |
seriesUID | Series identifier shared across all occurrences of a recurring item. |
occurrenceDate | The date of this particular occurrence. |
managedObjectURI | Internal object reference. |
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.
| Argument | Type | Required | Description |
|---|---|---|---|
includeDisabled | Boolean | No | Include 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.
| Argument | Type | Required | Description |
|---|---|---|---|
accountID | String | No | Limit 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.
| Argument | Type | Required | Description |
|---|---|---|---|
searchText | String | No | Full-text search query. |
calendarID | String | No | Limit results to a specific calendar. |
startDate | Date | No | Start of the date range. |
endDate | Date | No | End of the date range. |
mode | String | No | Query mode: "uiConsistent" (default) or "rawExhaustive". |
fetchLimit | Number | No | Maximum 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
| Mode | Behavior |
|---|---|
uiConsistent | Returns items matching what you see in BusyCal - respects checked/unchecked calendars and visibility settings. This is the default. |
rawExhaustive | Returns 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.
| Argument | Type | Required | Description |
|---|---|---|---|
title | String | Yes | Event title. |
startDate | Date | Yes | Event start date and time. |
endDate | Date | No | Event end date and time. If omitted, BusyCal uses a default duration. |
calendarID | String | No | Target calendar. If omitted, uses the default calendar. |
allDay | Boolean | No | Set to true for an all-day event. |
notes | String | No | Event notes. |
createTask
Creates a new task and returns the created task record.
| Argument | Type | Required | Description |
|---|---|---|---|
title | String | Yes | Task title. |
dueDate | Date | No | Due date and time. If omitted, creates an undated task. |
calendarID | String | No | Target calendar. If omitted, uses the default calendar. |
notes | String | No | Task 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.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The event's id from a query or create result. |
scope | String | No | Recurrence scope. Default: "all". |
startDate | Date | Yes | New 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.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The event's id. |
scope | String | No | Recurrence scope. Default: "all". |
title | String | No | New title. |
location | String | No | New location. Pass an empty string to clear it. |
notes | String | No | New 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.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
title | String | No | New title. |
notes | String | No | New notes. Pass an empty string to clear it. |
deferTask
Changes a task's due date. Returns the updated task record.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
dueDate | Date | No | New due date. Omit to make the task undated. |
completeTask
Marks a task as complete. Returns the updated task record.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
completedAt | Date | No | Completion timestamp. If omitted, uses the current time. |
uncompleteTask
Marks a completed task as not completed. Returns the updated task record.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
Deleting
deleteEvent
Deletes an event. Returns a record with the deleted item's id.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The event's id. |
scope | String | No | Recurrence scope. Default: "all". |
deleteTask
Deletes a task. Returns a record with the deleted item's id.
| Argument | Type | Required | Description |
|---|---|---|---|
id | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
Recurrence Scope
Commands that modify or delete items accept a scope argument to control which occurrences are affected:
| Scope | Meaning |
|---|---|
"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,
endDateis beforestartDatewhen creating an event, ormove eventis called with anendDateargument. - Item not found — the
idyou 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
idreturned from query or create commands when calling update, move, or delete commands. Do not construct IDs manually. - Because
idandisCompletedare 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
scopeexplicitly 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
supportsEventsorsupportsTasksfield fromlist calendars. - Every create and update command returns the resulting record, so you can chain operations by reading the
idfrom each result.