docs/Features/ICS-Calendar-Import.md
Issue: #6323
This feature lets you import an iCalendar (.ics) file — for example a Google
Calendar export — into a WeKan board. Each calendar event (VEVENT) becomes a
card whose start and due dates are populated, so the imported events
show up on the board's Calendar and Gantt views.
Scope: This is the MVP and is import-only (one direction:
.icsfile → WeKan cards). Two-way Google Calendar sync is NOT included. For the read-only export direction (WeKan → an iCal feed that Google Calendar can subscribe to), see the separatewekan-ical-serverproject.
For each VEVENT block:
| iCalendar property | WeKan card field |
|---|---|
SUMMARY | title |
DESCRIPTION | description |
DTSTART | startAt |
DTEND | dueAt (and endAt semantics) |
UID | kept on the parsed event (for de-dup / future sync) |
If an event has no DTEND, dueAt falls back to DTSTART so single-point
events still render on the calendar.
The core is a small, dependency-free, pure parser at
server/lib/icsImport.js. It uses only string
operations and regexes (no npm packages), which keeps it easy to unit-test and
safe to reuse.
It correctly handles:
VEVENT blocks in one file.YYYYMMDD (all-day events) and date-time values
YYYYMMDDTHHMMSSZ. Both are interpreted as UTC for deterministic,
timezone-independent parsing.\, → comma, \; → semicolon, \n / \N →
newline, \\ → backslash.DTSTART;VALUE=DATE:20261225.import { parseIcs, icsEventToCard, icsToCards } from '/server/lib/icsImport';
// 1. Parse raw .ics text into plain event objects.
const events = parseIcs(icsText);
// => [{ summary, description, start: Date|null, end: Date|null, uid }, ...]
// 2. Map one event to a WeKan card-shaped object.
const card = icsEventToCard(events[0], { boardId, listId, swimlaneId });
// => { title, description, startAt, dueAt, boardId, listId, swimlaneId }
// 3. Do both in one step.
const cards = icsToCards(icsText, { boardId, listId, swimlaneId });
icsToCards does not touch the database — it only produces card-shaped plain
objects. Persisting them is the caller's responsibility (see below).
A thin server method wires the parser into the existing Cards API:
server/methods/icsImport.js, registered
in server/imports.js.
// Client (or server) call:
Meteor.call(
'importIcsToBoard',
boardId,
listId,
swimlaneId,
icsFileText, // the raw contents of the uploaded .ics file
(err, res) => {
// res => { created: <number>, cardIds: [...] }
},
);
The method:
listId and swimlaneId belong to the board
(no cross-board writes).startAt / dueAt set.To wire a full upload UI, read the selected .ics file's text on the client
(e.g. with FileReader) and pass it as the icsText argument. A dedicated
upload form in the board import dialog is deferred (secondary to the parser
MVP).
Unit tests (mocha + chai, self-contained) are at
server/lib/tests/icsImport.tests.js
and cover: single and multiple events, all-day vs date-time forms, folded
lines, escaped text, icsEventToCard field mapping and the dueAt → start
fallback, and empty/garbage input.
Run with:
meteor test --once --driver-package meteortesting:mocha
icsToCards directly for
now).RRULE), attendees, alarms, and timezone (VTIMEZONE /
TZID) conversion — date-times are treated as UTC.