Skip to main content
Photo of Anton ShubinAnton ShubinSenior Full-Stack Engineer & Tech Lead

Self-hosting

The missing piece in a self-hosted CalDAV stack: a web UI for Tasks.org

Tasks.org syncs Android tasks to CalDAV. There is no web UI for that data. The fix is a stateless PWA on top of the CalDAV server you already run — and the architecture that made it boring to ship.

Anton Shubin9 min read

The tool: caldav-tasks-webCode: github.com/spy4x/caldav-tasks-web (opens in a new tab)

Contents

TL;DR

  • Update: the app works well with Radicale, but Stalwart support is broken and there is no public demo right now.
  • Tasks.org syncs Android tasks to CalDAV, but no web client existed for that data, so I built a stateless web app on top of the CalDAV server you already run.
  • The CalDAV server stays the only source of truth: the app stores only accounts and encrypted credentials, so switching servers moves no todos.
  • You don't need Nextcloud for it: the whole stack runs on one small box next to your CalDAV server.

Update, September 2026: The tool works well with Radicale. Stalwart support is currently broken, to the point that the app is not usable with it, and there is no public demo right now. Tracked in issue #10.

Tasks.org syncs Android tasks to CalDAV cleanly. There is no web UI for that data. After a year of reaching for my phone to edit a todo at my desk, I built one. Here is the gap I noticed in the self-hosted ecosystem, and how I closed it without breaking the standards-first contract most of us signed up for.

Tasks.org is the strongest Android task manager and it syncs to CalDAV. The data lives on a server you control. The moment you sit at a desk, that data is unreachable from a browser — no conformant client exists for the web.

The data is fine. The standard is fine. The clients are missing.

Why I didn't reach for Nextcloud Tasks

Link to section: Why I didn't reach for Nextcloud Tasks

Nextcloud is a whole suite, not a CalDAV server. People who chose Radicale or Stalwart on purpose shouldn't need to deploy a Nextcloud instance to edit a todo on desktop. The fix had to assume the CalDAV server is the only piece already running.

If you run Radicale — tested in production — that is your calendar server and this PWA is the missing task UI for it. Nextcloud and Baikal are CalDAV-compliant and expected to work too, though untested.

Architecture: CalDAV as the only source of truth

Link to section: Architecture: CalDAV as the only source of truth

VTODO files live on the CalDAV server. The PWA is a stateless client. SQLite in the API container holds user accounts and AES-GCM-encrypted server credentials and nothing else. Switch CalDAV servers and no todos move.

That property mattered enough to me that I tested it. In July 2026 the instance I ran then migrated from Radicale to Stalwart with a config string change. Zero todos moved. That is the test the architecture had to pass.

The read/write split maps cleanly onto CalDAV's GET versus PROPPATCH/PUT/DELETE. Each side tests in isolation; the CalDAV adapter is the only piece that knows the protocol. Radicale and Stalwart both speak through the same PROPFIND/PROPPATCH/PUT/DELETE interface, so adding a third server is a new adapter class, not a chain of if (serverType === ...) blocks.

The CalDAV XML responses vary more than they should. Radicale uses the default namespace and emits lowercase <d:response>. Stalwart emits uppercase <D:response> and <A:response> for the caldav namespace. Substring-matching "calendar" in the response body matches the user's principal home on top of actual calendars. The parsers have to be case-insensitive, prefix-tolerant, and filter calendars by <(prefix:)?calendar/> inside <resourcetype>. The bug-fix branch for this is its own commit on the public repo — anyone hitting the same issue against Stalwart will find it.

VTODO state changes constantly — status, priority, due date, percent complete, complete vs active, filters, sort, tag toggles. Signals give surgical UI updates without a virtual DOM tree of hooks to debug. A hook-based version of this project would have spent most of its life chasing re-render storms.

The hooks-vs-signals debate is a real one and the right answer depends on the app. For a heavily stateful list of items with frequent mutations and dense filtering, signals win on clarity and on raw update count. The codebase is roughly half the size it would be in React.

The deploy is a deno task deploy that rsyncs the project to a Hetzner box behind Traefik and runs docker compose up -d --build. Same Traefik that fronts the CalDAV server. Same SQLite file gets backed up by the same restic job that backs up the CalDAV data.

There is no Kubernetes. There is no service mesh. The whole stack fits on a single $20/month box and stays there. A real engineer reading this knows that's the right shape for a personal productivity tool.

What it does: lists every CalDAV calendar with its colors, renders a touch-first task list with filters and tags and due dates, full VTODO editing, Kanban view, recurring tasks, multi-server per account. Credentials encrypted with AES-GCM at rest. PWA so it installs on a phone home screen.

What it doesn't do: no sync engine (it is a client, not a server). Single user per install. No recurring-task editor yet. No native mobile wrapper. No team features.

I built it for myself. I am sharing it in case it is useful to someone else running the same stack.

A second pair of eyes on the CalDAV adapter against Nextcloud Tasks and Baikal. They speak the standard but emit different propstat shapes for properties Radicale and Stalwart happily skip. UI feedback, especially on tablets — I have tested on phone and desktop but not on a 10-inch iPad. If you run Tasks.org on Android, this pairs with it directly — open an issue if anything breaks.

Self-hosting is a stance, not a chore. Every SaaS-shaped tool that wants to be the source of truth is a small erosion of that stance. The gap in the self-hosted CalDAV stack was real, and fixing it didn't need to be enterprise-grade — it just needed to honour the contract: the server you trust, the format you control, the data you already have.

If you already run Radicale and want a desktop UI for the same data your Android phone sees, try it. The deploy script in the README is five commands.

Need this for your product?

Anton Shubin

Senior Full-Stack Engineer & Tech Lead

I build greenfield SaaS on a modern, lightweight stack, alone or with a team of senior developers from my own pool, and I cover full-stack, DevOps and architecture.

Get the next post by email

New posts on decisions for founders, AI and MCP, and self-hosting, about once a week at most, and only when there is one. Unsubscribe with one click.

Or follow the RSS feed