Skip to main content

radicale-ics-sync

Ever wanted to subscribe to an ICS feed (like a university timetable or a shared calendar) in Radicale, but still be able to edit events locally? That's what this plugin is for.

It syncs external ICS feeds into your Radicale calendars, keeps your local changes intact, and lets you filter out events you don't care about.

Installation

pip install radicale-ics-sync

Then configure Radicale to use the plugin (see Configuration).

Configuration

Radicale config

In your Radicale config file, set the storage type to radicale_ics_sync.storage:

[storage]
type = radicale_ics_sync.storage
filesystem_folder = /data/collections
# ics_config is optional; if omitted, defaults to /config/ics_sync.json
ics_config = /config/ics_sync.json

Sync jobs (ics_sync.json)

Create a file /config/ics_sync.json that defines which feeds to sync and where.

[
  {
    "feed": "https://example.com/calendar.ics",
    "collection": "username/calendar-name",
    "sync_interval": 3600,
    "include_patterns": [],
    "exclude_patterns": []
  }
]

You can provide multiple pairs of feed + collection. Each sync job supports these fields:

Field Required Default Description
feed ✅ — URL of the ICS feed
collection ✅ — Radicale collection path
sync_interval 3600 How often to poll the feed, in seconds
include_patterns [] Can be strings or arbitrary regex patterns
exclude_patterns [] Can be strings or arbitrary regex patterns

Note: The collection must already exist in Radicale before the plugin can sync to it. Create it through the Radicale web interface or your CalDAV client first.

Filtering

Patterns are matched case-insensitively against the event's SUMMARY (title) field.

{
  "feed": "https://university.example.com/timetable.ics",
  "collection": "alice/uni",
  "exclude_patterns": ["Tutorial", "Exercise"]
}
  • If include_patterns is set: only events matching at least one pattern are synced
  • If exclude_patterns is set: events matching any pattern are removed

Docker setup

Here is a minimal docker-compose.yml:

services:
  radicale:
    build: .
    container_name: radicale
    restart: always
    ports:
      - "5232:5232"
    volumes:
      - ./config:/config/config:ro  # Radicale config file (not a directory)
      - ./users:/config/users:ro
      - ./ics_sync.json:/config/ics_sync.json:ro
      - ./data:/data
    read_only: true
    tmpfs:
      - /tmp

And a Dockerfile to install the plugin:

FROM tomsquest/docker-radicale
RUN /venv/bin/pip install radicale-ics-sync --no-cache-dir

Non-Docker setup

Install Radicale and the plugin:

pip install radicale radicale-ics-sync

Create your Radicale config (e.g. ~/.config/radicale/config) and specify the path of your ics_sync.json:

[storage]
type = radicale_ics_sync.storage
filesystem_folder = ~/.local/share/radicale/collections
ics_config = ~/.config/radicale/ics_sync.json

Then create ~/.config/radicale/ics_sync.json and start Radicale:

radicale

State

The plugin stores its sync state in ics_sync_hashes.json, written next to your Radicale data. It tracks the last known content hash per event so unchanged events aren't rewritten. Safe to delete if you want a full re-sync on next startup.

Behavior

The plugin polls ICS feeds at a regular interval. Events are filtered by include/exclude patterns before being written to Radicale. Events that disappear from the upstream feed or are filtered out are deleted from the Radicale collection. Local edits to upstream events are preserved, until the upstream event itself changes, in which case the upstream version wins. Local events are not touched.

Limitations & Roadmap

  • Filtering is SUMMARY-only — other fields (LOCATION, DESCRIPTION) aren't supported yet, but are planned.
  • Upstream changes fully overwrite local edits — field-level merge (e.g. keeping your local title while accepting upstream time changes) is planned.

Contributing

Issues and pull requests are welcome.

License

MIT

Metadata

Release files for radicale-ics-sync 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for radicale-ics-sync 0.1.0
File Size Uploaded
radicale_ics_sync-0.1.0.tar.gz 11.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for radicale-ics-sync 0.1.0
File Interpreter ABI Platform
radicale_ics_sync-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 22.2 kB

Release files / radicale_ics_sync-0.1.0.tar.gz

Download URL radicale_ics_sync-0.1.0.tar.gz
Size 11.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b1f63b6a1a65fcae30e56a1e005bb891608366d8bf88bb252a648d89740e1e0e
BLAKE2b-256 checksum
How to use checksums
7cbfed337d557dfa923a88cea47a19721c904f603c6a578a37f325fdf738136f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / radicale_ics_sync-0.1.0-py3-none-any.whl

Download URL radicale_ics_sync-0.1.0-py3-none-any.whl
Size 10.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
924d22e5f1ae2150a9874a47137567e64847018b8269c8fe0a16c768fcbd46e0
BLAKE2b-256 checksum
How to use checksums
6c16e61c05d8b82911dff9a1ffc85e798b11220a06daf33bf6730db2cd1fb53c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page