Thunderbird add-on: groups the calendar list and can merge a group into one busy calendar
  • JavaScript 61.1%
  • Python 32.1%
  • CSS 4.8%
  • HTML 1.7%
  • Shell 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Lennart Kaiser 0adcd36ca8
All checks were successful
ci / test (push) Successful in 5s
ci / build (push) Successful in 10s
ci / e2e (push) Successful in 2m20s
Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list
Groups the calendar sidebar into collapsible groups and can merge a group
into one local read-only busy calendar. Writes only to calendars it created
itself (requireOwned), never to source calendars.

Includes unit tests, headless Thunderbird end-to-end and restart tests,
and a Forgejo Actions workflow (tests, xpi build, release on tags).
2026-10-23 19:13:38 +02:00
.forgejo/workflows Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
experiment Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
icons Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
options Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
screenshots Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
tests Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
.gitignore Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
background.js Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
build.sh Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
LICENSE Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
manifest.json Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
merge.js Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
README.md Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00
sidebar.css Calendar Groups 0.4.0: Thunderbird add-on that groups the calendar list 2026-10-23 19:13:38 +02:00

Calendar Groups (Thunderbird add-on)

Sorts the calendar list in the sidebar into collapsible groups. Optionally a group merges its calendars into one local calendar " busy": one hatched bar per busy period instead of lots of grey "(Public event)" tiles.

Tested with Thunderbird 157 and 158 (release channel).

Grouped calendar list, group "Rooms" merged

Install

The ready-made .xpi is attached to the releases. To build it yourself:

./build.sh                      # → dist/calendar-groups-<version>.xpi

Thunderbird → Add-ons and Themes → gear icon → "Install Add-on From File…" → pick the .xpi. The add-on is not signed; Thunderbird does not require that. It needs experiment permissions (full access) because it has to work on the calendar list.

Use

  • Create groups: right-click a calendar → "Calendar Groups settings…". Create and order groups there and assign calendars.
  • Move a calendar: right-click → "Move to group".
  • Collapse: click the group header.
  • Eye on the group: shows or hides all calendars of the group.
  • Merge icon on the group (appears on hover) or "Display: Merged" in the settings: the source calendars are hidden, the calendar " busy" is created and filled. Switching back to "Separate" deletes this calendar and shows the sources again.

Without groups the list looks as before. New calendars end up in "Ungrouped".

Settings

What the add-on changes (and what not)

The privileged interface (experiment/implementation.js) only offers:

Function Effect
getCalendars, getBusy read only
setVisible, hideSource, restoreSource local display setting calendar-main-in-composite (like the eye in Thunderbird); nothing goes to the server
syncOwnCalendar, removeOwnCalendar write only to local calendars (storage) that the add-on created and marked itself (kalendergruppen-owned); requireOwned() refuses everything else

There are no functions that create, change or delete other calendars' events. That is on purpose: for some calendar types Thunderbird's removeCalendar() deletes the data on the server, so it is only called through requireOwned().

The busy events have no attendees, no organizer and no reminders, so no invitations are sent. The busy calendar is read-only. Their description names neither the title nor the calendar of the source events, so a block copied into a shared calendar gives nothing away. When syncing, the add-on only deletes blocks it created itself (X-KALENDERGRUPPEN), and none from the past before the computed range.

Default calendar for new events: Thunderbird creates new events in the selected calendar, in day/week view by dragging even without a dialog. The add-on never leaves the selection on a hidden calendar or a "… busy" calendar: it switches to the default calendar set in the settings, otherwise to the writable one you clicked last, otherwise to the first local one. "source" and "busy" rows cannot be selected. If you select a read-only calendar, the selection therefore jumps back.

Busy blocks are marked private (CLASS:PRIVATE). If one is copied into a shared calendar, servers that respect this only show "private".

Sources are only hidden once the "… busy" calendar exists. If a source cannot be read while merging, everything stays visible as before. Disabled calendars do not take part. The status of each group is shown in the settings.

Your own entries in a "… busy" calendar (e.g. after switching off read-only) are never deleted: when switching back or uninstalling, the calendar is kept as "… (own entries)".

Unreadable source (error state, disabled): the group's blocks stay unchanged instead of showing the time as free.

Context menu: delete/unsubscribe in the menu act on the selected calendar. The add-on only opens the menu when Thunderbird verifiably points at the clicked calendar.

Disabling/uninstalling resets everything: the sources are shown again, the "… busy" calendars removed. A normal Thunderbird shutdown keeps the state.

If a new Thunderbird version no longer fits (internal modules missing), the add-on pauses, says so once and restores the hidden calendars via the settings (visible after a restart); its "… busy" calendars are disabled.

When Thunderbird changes its UI

At startup the add-on checks whether the calendar list is still built as expected. If not, it does not rebuild anything, shows a notice (once per Thunderbird version) and leaves the default list in place. The "… busy" calendars keep working because they are normal calendars. The status is also shown in the settings.

Notice when the calendar list is not recognized

Limits

  • No drag & drop in the grouped list. The order within a group follows Thunderbird's sorting.
  • Keyboard use of the list is limited (mouse and context menu work).
  • The merged range is 31 days back to 366 days ahead. It is recomputed when a source changes and every 6 hours.
  • "until HH:MM" in the title uses the system time zone.
  • If you create an event in a hidden source, Thunderbird shows it again. The add-on does not hide it again (eye on the row).
  • If you toggle "offline support" on a hidden source, Thunderbird recreates it with a new ID; it then stays hidden until you show it yourself.
  • An add-on update rebuilds the "… busy" calendars.
  • At Thunderbird startup the first recomputation waits 20 seconds until network calendars are loaded; until then the old blocks stay. After an add-on update the sources are visible until the blocks are recomputed.
  • During a sync the "… busy" calendar is briefly writable.
  • Blocks older than a good year are removed.
  • The fallback path for incompatible Thunderbird versions is not tested automatically (it cannot be produced in a test).

Tests

node --test tests/                                 # merge logic
python3 tests/e2e.py /tmp/cg-test                  # full flow
./build.sh && python3 tests/restart_test.py /tmp/cg-test   # .xpi, restart, disable

The end-to-end tests start their own headless Thunderbird with a throwaway profile (-no-remote, Marionette on port 28291), never your real profile. They create local calendars and a writable ICS calendar served by a local HTTP server, and check at the end:

  • no addItem/modifyItem/deleteItem calls on source calendars (spy on the provider classes, with a cross-check that it sees changes),
  • only display settings changed on source calendars,
  • no PUT/DELETE to the network calendar.

CI

.forgejo/workflows/ci.yml runs all tests on every push (the end-to-end tests in an Arch container with the current Thunderbird) and builds the .xpi. A tag vX.Y.Z matching the version in manifest.json creates a release with the .xpi attached.

License

MIT, see LICENSE.