gdav

A sans-io CalDAV and CardDAV client library for Gleam.

Package Version Hex Docs

This package uses the sans-io approach, meaning it does not send HTTP requests itself. Instead it gives you functions for building HTTP requests and decoding HTTP responses, and you send the requests with an HTTP client of your choosing.

This HTTP client independence gives you full control over HTTP, and means this library works on both the Erlang and JavaScript runtimes.

gleam add gdav@1

Authentication

Basic — username and password, works with most self-hosted servers:

let assert Ok(creds) =
  gdav.credentials("https://dav.example.com/")
let creds = gdav.with_basic_auth(creds, "username", "password")

Bearer — OAuth2 access token, required for Google Calendar, Fastmail, and other hosted providers:

let assert Ok(creds) = gdav.credentials("https://dav.example.com/")
let creds = gdav.with_bearer_auth(creds, "your_oauth_access_token")

Quickstart — known collection path

If you already know your collection URL (typical for self-hosted servers like Radicale, Baikal, DaviCal, Nextcloud), skip discovery and go straight to operations:

import gdav
import gdav/get_all_events
import gleam/hackney
import gleam/io

pub fn main() {
  let assert Ok(creds) = gdav.credentials("http://localhost:port/")
  let creds = gdav.with_basic_auth(creds, "admin", "admin")

  let assert Ok(res) =
    get_all_events.request("/admin/my-calendar-uuid")
    |> get_all_events.build(creds)
    |> hackney.send

  let assert Ok(events) = get_all_events.response(res)

  echo events
}

Discovery — finding URLs automatically

For servers where you only know the base URL, use the discovery chain to find the principal and home URLs, then list collections.

import gdav
import gdav/discover_principal
import gdav/discover_calendar_home
import gdav/list_calendars
import gdav/get_all_events
import gleam/hackney

pub fn main() {
  let assert Ok(creds) = gdav.credentials("https://dav.example.com/dav.php")
  let creds = gdav.with_basic_auth(creds, "username", "password")

  // Step 1: find the user's principal URL
  let assert Ok(res) =
    discover_principal.request()
    |> discover_principal.build(creds)
    |> hackney.send
  let assert Ok(principal) = discover_principal.response(res)

  // Step 2: find the calendar home URL
  let assert Ok(res) =
    discover_calendar_home.request(principal)
    |> discover_calendar_home.build(creds)
    |> hackney.send
  let assert Ok(home) = discover_calendar_home.response(res)

  // Step 3: list all calendars
  let assert Ok(res) =
    list_calendars.request(home)
    |> list_calendars.build(creds)
    |> hackney.send
  let assert Ok(calendars) = list_calendars.response(res)

  // Step 4: use a calendar's href for operations
  let assert [calendar, ..] = calendars
  let assert Ok(res) =
    get_all_events.request(calendar.href)
    |> get_all_events.build(creds)
    |> hackney.send
  let assert Ok(events) = get_all_events.response(res)
}

Service discovery (.well-known)

For hosted providers (Google Calendar, Apple Calendar) the actual CalDAV endpoint differs from the server’s main domain. Use discover_service to probe /.well-known/caldav and follow the redirect to the real root URL.

import gdav/discover_service

let assert Ok(res) =
  discover_service.request(discover_service.CalDAV)
  |> discover_service.build(creds)
  |> hackney.send

let root_path = case discover_service.response(res) {
  Ok(discover_service.Redirected(url)) -> url
  _ -> creds.path
}

let assert Ok(res) =
  discover_principal.request_at(root_path)
  |> discover_principal.build(creds)
  |> hackney.send

For self-hosted servers this step is usually unnecessary — they either don’t expose /.well-known or it points straight back to the URL you already have.

CalDAV operations

CardDAV operations

Shared operations

Further documentation can be found at https://hexdocs.pm/gdav.

Search Document