CalDAV (Calendar Sync)

bext can serve as a CalDAV server (RFC 4791), enabling calendar synchronization with any standards-compliant client — Apple Calendar, Thunderbird, GNOME Calendar, DAVx5, and others.

Enabling CalDAV

[caldav]
enabled = true
storage = "memory"   # "memory" | "pg"

# For PostgreSQL storage
# storage = "pg"
# pg_url = "postgresql://user:pass@localhost/bext_dav"

Compile with the caldav feature flag:

bext build --features caldav

WebDAV Foundation

CalDAV is built on WebDAV. bext implements the required HTTP methods:

Method Purpose
PROPFIND Read properties of calendars and events.
PROPPATCH Modify calendar properties.
REPORT Run queries (time-range, multiget, sync).
MKCALENDAR Create a new calendar collection.
PUT Create or update a calendar event.
GET Retrieve a single event (.ics).
DELETE Remove an event or calendar.

URL Structure

Calendar collections follow a predictable hierarchy:

/calendars/{user}/                      # User's calendar home
/calendars/{user}/{calendar}/           # A specific calendar
/calendars/{user}/{calendar}/{uid}.ics  # A single event

Example:

/calendars/alice/work/
/calendars/alice/work/meeting-2026-04.ics
/calendars/alice/personal/

iCalendar Format

Events are stored as iCalendar (RFC 5545) .ics files. bext parses and validates them on PUT:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//bext//CalDAV//EN
BEGIN:VEVENT
UID:meeting-2026-04@example.com
DTSTART:20260415T090000Z
DTEND:20260415T100000Z
SUMMARY:Team Standup
END:VEVENT
END:VCALENDAR

Supported component types:

Component Description
VEVENT Calendar events.
VTODO Tasks / to-do items.
VJOURNAL Journal entries / notes.

CalDAV Reports

Clients query calendars using REPORT requests with XML bodies. bext supports three report types:

calendar-query

Filter events by property or time range:

<c:calendar-query xmlns:c="urn:ietf:params:xml:ns:caldav">
  <d:prop xmlns:d="DAV:">
    <d:getetag/>
    <c:calendar-data/>
  </d:prop>
  <c:filter>
    <c:comp-filter name="VCALENDAR">
      <c:comp-filter name="VEVENT">
        <c:time-range start="20260401T000000Z"
                      end="20260430T235959Z"/>
      </c:comp-filter>
    </c:comp-filter>
  </c:filter>
</c:calendar-query>

calendar-multiget

Fetch specific events by URL in a single request:

<c:calendar-multiget xmlns:c="urn:ietf:params:xml:ns:caldav">
  <d:prop xmlns:d="DAV:">
    <d:getetag/>
    <c:calendar-data/>
  </d:prop>
  <d:href>/calendars/alice/work/meeting-2026-04.ics</d:href>
  <d:href>/calendars/alice/work/review-2026-04.ics</d:href>
</c:calendar-multiget>

sync-collection

Efficient incremental sync (RFC 6578). The client sends a sync token from the previous sync, and bext returns only changes since then:

<d:sync-collection xmlns:d="DAV:">
  <d:sync-token>data:,12345</d:sync-token>
  <d:prop>
    <d:getetag/>
  </d:prop>
</d:sync-collection>

bext responds with new/modified/deleted resources and a fresh sync token. This avoids full re-fetch on every sync cycle.

Discovery

Clients discover CalDAV using the .well-known redirect (RFC 6764):

GET /.well-known/caldav
→ 301 /calendars/

bext registers this redirect automatically when CalDAV is enabled. Client apps only need the server hostname to find the calendar home.

Storage Backends

Backend Description
memory In-memory store. Fast, lost on restart.
pg PostgreSQL via @bext/infra-pg. Persistent.

The PostgreSQL backend shares its schema with CardDAV (contacts), so both features use the same database.

Authentication

CalDAV endpoints require authentication. bext uses the same auth middleware as HTTP routes — JWT, API keys, or OAuth:

[[routes]]
path = "/calendars/*"
auth = "required"

The {user} in the URL path is matched against the authenticated identity. Users can only access their own calendars unless granted access via sharing.

Configuration Reference

[caldav]
enabled = true
storage = "memory"
max_event_size_bytes = 65536
max_events_per_calendar = 50000
Field Default Description
storage memory Storage backend.
max_event_size_bytes 65536 Max size of a single .ics upload.
max_events_per_calendar 50000 Safety limit per calendar.

Feature Flag

CalDAV requires the caldav feature flag:

bext build --features caldav
Warning

The memory storage backend loses all calendar data on restart. Use storage = "pg" for any deployment where data persistence matters. The PostgreSQL backend shares its schema with CardDAV, so both features use the same pg_url.

Tip

Client apps only need the server hostname — the .well-known/caldav redirect handles discovery automatically. Pointing clients at /calendars/ directly bypasses this and may break auto-configuration in some apps (Apple Calendar, DAVx5).

Related

Links