ex_dav
⚠️ Beta / heavy WIP. APIs are unstable and may change between releases. Not yet recommended for production use.
CalDAV server library for Elixir. Mounts as a Plug under any host
application. Ships with a Postgres-backed reference adapter and an
in-memory adapter; bring your own for arbitrary backends.
plug ExDav.CalDav.Plug,
storage: ExDav.Storage.Postgres,
authenticator: {ExDav.Authenticator.Basic,
verify: {ExDav.Storage.Postgres, :authenticate}}What you get
ExDav.CalDav.Plug— handlesPROPFIND,REPORT,MKCALENDAR,PROPPATCH,PUT,GET,DELETEunder/dav/...plus the RFC 6764.well-known/caldavredirect. Other paths pass through untouched.ExDav.Storage— behaviour for storage adapters. Adapters return plain maps so the protocol layer stays ORM-agnostic.ExDav.Storage.Postgres— reference adapter using Ecto. Owns its own users / calendars / objects / tombstones tables; reads itsEcto.RepofromApplication.fetch_env!(:ex_dav, :repo).ExDav.Storage.Memory— in-memory adapter (Agent-backed) for dev demos and tests.ExDav.Authenticator— behaviour for HTTP authenticators.ExDav.Authenticator.Basicships HTTP Basic with a configurable:verifyMFA tuple.
Usage in a Phoenix app
# config/config.exs
config :ex_dav, repo: MyApp.Repo
# lib/my_app_web/endpoint.ex
plug ExDav.CalDav.Plug,
storage: ExDav.Storage.Postgres,
authenticator: {ExDav.Authenticator.Basic,
verify: {ExDav.Storage.Postgres, :authenticate}}
Run ExDav.Storage.Postgres's migrations into your repo (copy from
apps/ex_dav/priv/repo/migrations — these will move into the library
itself in a future release).
Custom storage adapter
defmodule MyApp.CalDavStorage do
@behaviour ExDav.Storage
@impl true
def list_calendars(username) do
# return [%{name:, displayname:, description:, components:, ctag:, objects:}]
end
@impl true
def put_object(username, cal_name, obj_name, ical), do: ...
# ...all 11 callbacks
endThen mount:
plug ExDav.CalDav.Plug,
storage: MyApp.CalDavStorage,
authenticator: {MyApp.CalDavAuth, []}Custom authenticator
defmodule MyApp.CalDavAuth do
@behaviour ExDav.Authenticator
@impl true
def authenticate(conn, _opts) do
case extract_token(conn) do
{:ok, principal} -> {:ok, conn, principal}
:error -> :unauth
end
end
endStandalone
For a runnable reference deployment, see apps/ex_dav_server in this
repository — a minimal Phoenix app that wires the library to Postgres
and ships an admin LiveView for managing CalDAV principals.