Documentation

Last updated: 2026-11-12

CalDAV behavior and storage reference

This page covers Everlock's CalDAV behavior and storage model.

What it supports

  • served through frontend-http
  • multi-instance and multi-vhost
  • one versioned-storage store per instance
  • Everlock-user HTTP authentication
  • Everlock access control on http/calendar/<instance>
  • multiple calendars per instance
  • writable CalDAV collections and event resources
  • calendar names, descriptions, colours and time zones set by MKCALENDAR, extended MKCOL and PROPPATCH, and any other calendar property a client sets — Apple Calendar's calendar order among them — kept and given back
  • readable .ics event files in versioned storage
  • recurrence-aware calendar-query with the full CalDAV filter
  • calendar-data with expand
  • calendar-multiget
  • sync-collection with commit-id sync tokens
  • free-busy-query

URL shape

/.well-known/caldav
/caldav/
/caldav/principals/me/
/caldav/calendars/
/caldav/calendars/<calendar>/
/caldav/calendars/<calendar>/<event>.ics

Stores and vhosts

store=<name> picks the store explicitly and is used as given — an existing store, a hosted git repository or a store another instance uses included — as long as it is a valid name: lowercase letters, digits and -, not first or last. Without store=, the store is the instance name, and /calendar create refuses it when a store or repository of that name already exists, another instance uses it, or Everlock reserves it (everlock-system, everlock-mail and every backend's bootstrap store such as calendar and everlock-contacts). The refusal says what the name collides with; pass store=<name> to use it on purpose, or pick another name.

Instances that share a store serve the same data: deleting a calendar through one deletes it for all.

A vhost belongs to one calendar instance; /calendar create and /calendar set refuse a vhost another instance maps, comparing without regard to case. The creating user becomes Owner of the new instance. When two configured instances list one vhost, the first by name serves it and a warning is logged. A config file that is not TOML stops the backend from starting, while a single malformed instance entry is skipped with a warning.

CalDAV lives under /caldav, with discovery at /.well-known/caldav. An instance holds those two paths on each of its vhosts and nothing else, so a site, a file share or the contacts backend serves the rest of the same host; /calendar create and /calendar set refuse a vhost where another backend already holds one of the two paths. Virtual hosts explains how backends share a host.

.local vhosts are announced over mDNS as CalDAV services with path=/caldav as soon as an instance takes them on, and withdrawn when it gives them up.

A vhost inside a zone the DNS backend serves carries the RFC 6764 records clients look up when given just the vhost name: _caldavs._tcp.<vhost> SRV 0 1 <HTTPS port> <vhost>. with a TXT record path=/caldav beside it, and _caldav._tcp.<vhost> SRV 0 0 0 ., as CalDAV runs over HTTPS. The port is the one the HTTP frontend serves HTTPS on, and the records follow a vhost from the moment /calendar create or /calendar set saves it.

Storage shape

Each calendar instance uses the versioned-storage store it names:

default/
  config.toml
  team-meeting.ics
work/
  config.toml
  customer-review.ics

Each event is one .ics file named by the URL the client writes it to. The file holds what the client sent, with CRLF line breaks and a UID added where one is missing; when that changed the body, the answer to the write carries no ETag, and the client fetches the stored version. Collection sync uses the underlying store commit id as the sync token, and the token a sync hands out stands for exactly the events it reports.

A conditional write (If-Match, If-None-Match) is checked again as it is committed, so of two clients writing the same event against the same version, the second to commit gets 412 and the first one's edit stays.

Calendar properties

A calendar's name, description and colour live in its config.toml. Every other property a client sets on a calendar is kept there as sent and comes back when a client asks for it: Apple Calendar's calendar-order keeps the order of your calendars across devices. PROPPATCH with remove deletes one. Properties the server works out itself — the ETag, the sync token, the resource type — are refused with 403, and the request then changes nothing.

calendar-timezone sets the zone floating times and all-day events are read in when the TZID of its VTIMEZONE is an IANA name such as Europe/Berlin. The value is kept as sent in any case, so a Windows zone name is stored and returned too, and the calendar keeps its zone.

A PROPFIND gets the properties it names, and a 404 for each one the calendar or event lacks; a request for everything gets the full set, with calendar-timezone given when a client names it.

Event validation

  • require one VCALENDAR
  • require one master VEVENT; related recurrence overrides may share the same resource and UID
  • require VERSION:2.0
  • require DTSTART
  • preserve UID or inject one if missing; an update without one keeps the event's
  • refuse a UID another event of the same calendar uses, and a write that changes the UID of an existing event, with 403 no-uid-conflict

The UID check looks the UID up in a set of hashes Everlock keeps in memory per calendar, built at the calendar's first new event; it reads the calendar only to name the event that holds a UID. Bulk uploads stay fast, and a change made to the store by other means is noticed at the next new event.

That stays intentionally permissive because Android and Apple calendar clients do not always emit identical event shapes.

Query behavior

  • recurrence fields such as RRULE and EXDATE are preserved
  • queries apply the CalDAV filter as written: component, property and parameter filters, text matches with negation, and time ranges with a start, an end or both
  • an event is in a time range when one of its instances overlaps it, cancelled ones included; free/busy counts cancelled and transparent time as free
  • recurring events are expanded at query time for range-based views, and a client asking for expand gets each instance as an event of its own, in UTC
  • IANA timezones, floating times, daylight-saving transitions, recurrence exclusions, additional dates, and detached instance overrides are applied during expansion
  • free/busy answers are derived from the stored events
  • sync uses commit history instead of a second sync database; a sync token the calendar cannot sync from makes the client start over with a full sync
calendar backend caldav