Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Option reference

Every option the config parser accepts. Durations use humantime syntax ("30s", "15m", "1h 30m"). Unknown keys are errors.

[daemon]

The table and all its options are optional.

OptionTypeDefaultDescription
state_dirpath$CAPYCAL_STATE_DIR, else Linux ~/.local/state/capycal, macOS ~/Library/Application Support/capycal/stateState files and config locks. Wins over CAPYCAL_STATE_DIR
log_levelstring"info"trace, debug, info, warn, error or a tracing filter directive. RUST_LOG and -v take precedence. Applied again on reload
log_format"pretty" | "json""pretty"CLI log output format
watch_configboolfalseCLI daemon: reload when the config file changes. The desktop app always does

Details: Daemon settings.

[accounts.<name>]

OptionTypeDefaultDescription
urlstringrequiredServer address to discover the account’s calendars on
usernamestringrequiredHTTP Basic user name
passwordcredentialrequired"plain", { env = "VAR" }, { file = "path" }, { command = "..." } or { keychain = "name" }, see Credentials

Details: Calendars.

[calendars.<name>]

OptionTypeDefaultDescription
accountstringrequired for CalDAVName of the [accounts.<name>] whose credentials the calendar uses
urlstringrequired for CalDAVCalDAV calendar collection URL
eventkit_calendarstring-macOS only. Calendar identifier, "Account/Name" or name. Makes this a Calendar.app calendar
ics_urlcredential-Address of a read-only iCalendar feed (https://, http:// or webcal://), as a string or any credential form. Makes this a feed, usable only as a source; takes no account or url. Details: Calendar feeds
concurrencyinteger2Maximum events written to this calendar at the same time as a target, shared by all syncs writing to it; also the parallel deletes of --purge / --purge-all. 0 counts as 1

Details: Calendars, Credentials.

[syncs.<name>]

Each sync is a table named by its key, for example [syncs.work-to-home]. The name is non-empty, has no / or \ and no leading .. Names with spaces, dots or other characters outside A-Z a-z 0-9 _ - need quotes: [syncs."work to home"]. Two syncs with the same name are a TOML error (“duplicate key”). Syncs run, log and show up in the menu bar app in file order.

OptionTypeDefaultDescription
sourcestringrequiredCalendar key to read from
targetstringrequiredCalendar key to write to; must differ from source
intervaldurationrequiredTime between sync cycles
fieldslist of strings[]Content properties to copy, such as "SUMMARY", "LOCATION", "DESCRIPTION"; "VALARM" keeps alarm components. DTSTART, DTEND, DURATION, RRULE, RDATE, EXDATE, STATUS, TRANSP, UID, DTSTAMP and RECURRENCE-ID are always kept. See fields
enabledbooltruefalse keeps the sync configured but stops it. See Disabling a sync
full_sync_intervalduration"7d"Time between automatic full syncs; must be greater than zero. See Full sync
eventkit_days_pastinteger30Calendar.app source: days before now to query. Together with eventkit_days_future at most 1460
eventkit_days_futureinteger365Calendar.app source: days after now to query
eventkit_all_eventsboolfalseCalendar.app source: query 20 years back and 4 years ahead in chunks
transformslist of tables[]Transforms, run in order. See Transforms below

Details: Syncs.

Transforms

A sync’s transforms are the transforms list in its table, one inline table per transform:

[syncs.work-to-home]
# source, target, ...
transforms = [
  { field = "SUMMARY", action = "set_field", value = "Busy" },
  # comments are allowed between entries
  { action = "add_alarm", trigger = "10m" },
]

Multi-line lists of inline tables need TOML 1.1, which Capycal reads. Some editors and linters still only know TOML 1.0 and flag them. The TOML 1.0 form, one [[syncs.<name>.transforms]] table per transform, works too:

[[syncs.work-to-home.transforms]]
field = "SUMMARY"
action = "set_field"
value = "Busy"

A sync uses one form or the other; mixing both for the same sync is a TOML error.

OptionTypeUsed byDescription
actionstringall, requiredset_field, replace_field, replace_field_regex, remove_field, add_buffer, add_alarm, skip
fieldstringset_field, replace_field, replace_field_regex, remove_field (required)iCal property name, e.g. "SUMMARY"
valuestringset_field (required)New value, may contain placeholders
patternstringreplace_field_regex (required)Regular expression (Rust regex syntax)
searchstringreplace_field (required)Text to find, not empty
replacementstringreplace_field, replace_field_regex (required)Replacement, may contain placeholders; for replace_field_regex also $1 / ${name}
countintegerreplace_fieldReplace only the first count occurrences, default all
pre_eventdurationadd_bufferMove the start earlier
post_eventdurationadd_bufferMove the end later. At least one of the two is required
triggerdurationadd_alarm (required)Alarm time before the start
whenlist of conditionsall; skip (required)The transform only changes events that match all of them; { any = [...] } is one of them that matches when any of its conditions does

Placeholders in value and replacement: {{source_name}}, {{source_url}}, {{sync_name}}. Details: Transforms.

Conditions

when is a list of inline tables. In the table form, each condition is a [[syncs.<name>.transforms.when]] table after its transform. A condition has exactly one test: contains, matches or empty (with field), days (with optional start_time and end_time), or all_day. Or it is { any = [...] }, at least two such conditions of which one has to match, with no other keys.

OptionTypeUsed byDescription
anylist of conditionsAt least two conditions, one of them has to match; nothing else in the same table, no any inside
fieldstringcontains, matches, empty (required)iCal property name in upper case, e.g. "LOCATION"
containsstringThe unescaped value contains this text, not empty
matchesstringThe unescaped value has a match of this regex, not empty
emptybooleantrue: the property is missing or empty; false: it has a value
dayslist of stringsThe event starts on one of "mon" … "sun", at least one
start_time"HH:MM"daysWindow start, local time, default "00:00"; after end_time the window wraps around midnight
end_time"HH:MM"daysWindow end (inclusive), local time, default "23:59"
all_daybooleantrue: the event is all-day; false: it is not
notbooleancontains, matches, daysTurn the test around, default false

Details: Conditions.