Wekan
Open source kanban board application built with Meteor
Alternative to: trello
v9.84
2026-07-11v9.84 2026-07-11 WeKan ® release
This release adds the following features and fixes:
-
Fix scheduled backup cron init crashing at server startup on Meteor 3:
The scheduled-backup cron used Meteor’s synchronous Mongo API (
BackupSettings.findOne/upsert), which Meteor 3 no longer allows on the server — startup loggedfindOne is not available on the server. Please use findOneAsync() instead.and the cron silently never registered, so scheduled backups did not run.registerCron()is nowasyncand usesfindOneAsync, thegetBackupSchedule/saveBackupSchedulemethods usefindOneAsync/upsertAsync, and everyregisterCron()caller awaits it. Addedtests/backupCron.test.cjs(positive + negative cron-registration tests and a source guard that fails if the synchronous server-forbidden API is reintroduced), wired into thetest:unit:nodesuite. -
Snap: show the correct writable path for parallel installs in backup instructions:
The backup/migration help text printed by
wekan.helphardcoded/var/snap/wekan/common/files, which is wrong for a parallel snap install (e.g.wekan_customer, whose data lives under/var/snap/wekan_customer/common). It now prints$SNAP_COMMON/files, which snapd sets per instance. Display-only; all functional snap paths already derive from$SNAP_COMMON/$SNAP_DATA, so parallel installs were already fully supported. -
Fix flaky server-side Mocha i18n test (TAPi18n
.loadLanguage):The
.loadLanguagesuite stubbedaddResourceBundleon the sharedTAPi18n.i18nsingleton and asserted the call count. BecauseloadLanguage()readsthis.i18nlate and each test re-init()s the singleton, cross-test state could leave the stub watching a different i18next instance than the one the bundle was registered on, so the count read 0 and the run intermittently reportedexpected addResourceBundle to be called once. The tests now assert on observable i18next state (hasResourceBundle/getResourceBundleunder the normalisedtoI18nCode), which is instance-agnostic and deterministic and mirrors the reliable.setLanguagesuite. Also stopped a leakedTracker.autorunin the.getLanguagereactive test (the cross-test hazard). -
The extra-arch bundle build ran
node:24-slimunder QEMU, but the officialnode:24image publishes nolinux/s390xmanifest (only amd64/arm64/ppc64le), so the s390x leg failed with “no matching manifest for linux/s390x”. The emulated native-module rebuild now runs on anubuntu:26.04base (which publishes every arch) and installs Node.js from nodejs.org; riscv64 uses unofficial-builds.nodejs.org (nodejs.org ships no riscv64).MongoDB Community only ships amd64/arm64 server binaries, so on ppc64le, s390x and riscv64 — the other architectures with a Node.js 24 build — WeKan now bundles FerretDB v1 (the wekan/FerretDB fork with its embedded pure-Go SQLite backend, which speaks the MongoDB wire protocol) instead of requiring MongoDB. The FerretDB binary is cross-compiled once for all five architectures (CGO off, static, no QEMU) and embedded in every
.zipnext tomain.js, so amd64/arm64 users can opt in too withWEKAN_DB=ferretdb. The Docker image now covers all five architectures and auto-starts FerretDB on the MongoDB-less ones. FerretDB telemetry is disabled and locked (--telemetry=disable, plusDO_NOT_TRACK). armv7l/32-bit ARM is still not built: there is no Node.js 24 build for it anywhere, so nothing could run WeKan there regardless of RAM. -
The WeKan snap gains a
databasesetting (mongodborferretdb). A newferretdbservice runs FerretDB v1 (SQLite) on the same port MongoDB would use, soMONGO_URLis unchanged; only one database runs at a time, and the configure hook stops one and starts the other when the setting changes. Switch withsnap set wekan database=ferretdb(or back withdatabase=mongodb).New snap installs default to FerretDB on all platforms (via the install hook, which runs only on fresh installs — upgrades keep MongoDB, and a pre-existing MongoDB data directory is detected and kept so no data is lost). There is no
snap install --db=ferretdbflag (snapd has no custom install options); use the two-stepsnap install wekan --channel=latest/betathensnap set wekan database=ferretdb.mongosh collects anonymized usage analytics by default and the snap invokes it several times; it is now disabled so nothing phones home. mongod itself has no phone-home telemetry (Cloud Free Monitoring is opt-in and stays off).
-
Admin Panel / Version: show database type, version, commit, storage engine and reactivity mode:
Admin Panel / Version now shows whether WeKan is using MongoDB or FerretDB v1 (SQLite), the MongoDB-compatible version and the server git commit, FerretDB’s own version and commit when FerretDB is in use, the storage engine, and which reactivity mechanism is currently in use — changeStreams / oplog / polling (FerretDB has no oplog, so it uses polling).
-
FerretDB v1 (the wekan/FerretDB fork, pure-Go SQLite backend, CGO off, no QEMU) is now cross-compiled for every platform Go and modernc.org/sqlite support — far beyond the arches Node.js ships — and packed into a single
ferretdb.zip:ferretdb/<arch>/ferretdb-<arch> (Linux/macOS/BSD, executable) ferretdb/<arch>/ferretdb-<arch>.exe (Windows only) ferretdb/README.md (links to https://github.com/wekan/FerretDB)That zip is produced and released in the wekan/FerretDB repo (by its
build.sh, which gained sequential and parallel “Build ferretdb.zip” menu options). The WeKan release workflow no longer builds FerretDB with Go: it downloads the newestferretdb.zipfrom https://github.com/wekan/FerretDB/releases and every WeKan build (the bundle .zip for each arch, and via the bundle the Docker image and snap, including the Windows and macOS bundles) embeds its per-arch binary from that one source. ferretdb.zip is not re-attached to the WeKan releases. -
Fix #6445: dynamic-import chunks 404 under a sub-path (duplicated build-chunks/build-chunks/):
Under a sub-path deployment (ROOT_URL like
https://host/wekan, usually behind a reverse proxy that strips the prefix), language selection and other lazy-loaded features failed withENOENT ... build-chunks/build-chunks/<id>.js. rspack’s client runtime builds each chunk URL as public-path + chunk-name, and the chunk name already carries thebuild-chunks/prefix, but client/00-startup.js set the sub-path public path to<sub-path>/build-chunks/, so rspack appended a secondbuild-chunks/. It now sets the public path to just<sub-path>/and lets rspack addbuild-chunks/itself. -
Add snapcraft-core24.yaml so the newest WeKan can be published to the Snap Stable channel:
The main
snapcraft.yamlusesbase: core26, which (until core26 is released) needsbuild-base: devel+grade: devel, so it can only go to Snap Beta/Edge.snapcraft-core24.yamlbuilds the SAME newest WeKan (Meteor 3.5, Node.js 24, FerretDB v1, MongoDB 7, Caddy 2) onbase: core24(a released base,grade: stable), so the Snap Stable channel can finally be updated from the old 6.09 snap. Only base/build-base/grade differ. -
Self-contained release bundles: bundle Node.js + FerretDB + start-wekan.{sh,bat}:
Each
wekan-<version>-<arch>.zipis now fully offline. Itsbundle/directory contains the WeKan server, a Node.js binary for that platform, a FerretDB v1 (SQLite) binary, and astart-wekan.sh(start-wekan.baton Windows) that by default runs WeKan on the bundled Node against the bundled FerretDB SQLite, storing data and attachments/avatars on the filesystem underWRITABLE_PATH(as in the Windows Offline guide) — no separate Node or database install needed. The Docker image and snap, which have their own Node and entrypoint, strip the redundant bundled Node + launchers to stay small. -
Sandstorm packaging (mirroring
releases/release-sandstorm.sh+install-sandstorm.sh: installs Meteor, meteor-spk 0.6.0 and a dev Sandstorm, runsmeteor-spk pack) is not tested well enough yet, so it no longer runs as part of a full release — thebuild-sandstormjob was removed fromrelease-all.yml. Instead a separate, manually-triggeredsandstorm.ymlbuilds ONLY the.spkand uploads it aswekan-sandstorm-YYYY_MM_DD-HH_MM_SS.spkto the newest WeKan GitHub Release (plus a workflow artifact), so it can be downloaded and tested for errors without affecting releases. Experimental in CI — Sandstorm needs unprivileged user namespaces, and signing the.spkneeds the app private key via theSANDSTORM_KEYRINGsecret;spk publish/ scp upload stay manual. -
Migration: resumable progress in WRITABLE_PATH + compact the old MongoDB after success:
The MongoDB → FerretDB / GridFS → filesystem migrator (used by the Snap and Sandstorm migrations) now checkpoints progress to
$WRITABLE_PATH/migration-progress.jsonafter every collection and file phase, so an interrupted migration (snap refresh, Sandstorm grain restart, power loss) resumes on restart instead of starting over — skipping collections already copied. Once migration has completed, a later boot reclaims the now-duplicated disk space in the old MongoDB by runningcompacton each source collection (best-effort, once). The existing ROOT_URL progress dashboard and disk-space checks are kept, and the dashboard is restored from the checkpoint on resume. -
Snap: migrate the Caddyfile from Caddy v1 to Caddy v2 format on upgrade:
When upgrading from an old WeKan snap,
$SNAP_COMMON/Caddyfilemay still be in Caddy v1 syntax, which Caddy 2 cannot parse (caddy would fail to start).caddy-controlnow runs a converter beforecaddy runwhen caddy is enabled: a no-op if the file already parses as Caddy 2, otherwise it backs up the original, converts the common v1 directives (proxy / TARGET→reverse_proxy TARGET, drop the v1websocket/transparentpresets Caddy 2 does by default,gzip→encode gzip, strip thehttp://scheme), validates withcaddy adapt, and only keeps a valid result — falling back to the shipped Caddy 2 template otherwise, so caddy always starts with valid config. -
Rename docker-compose-ferretdb.yml to -v2-postgresql.yml and add -v1-sqlite.yml:
docker-compose-ferretdb.yml(FerretDB 2 + PostgreSQL) is renamed todocker-compose-ferretdb-v2-postgresql.yml, and a newdocker-compose-ferretdb-v1-sqlite.ymlruns WeKan against FerretDB v1 with the embedded SQLite backend — no PostgreSQL or MongoDB — fetching the v1 binary for the container’s architecture from the newest wekan/FerretDB release. Both compose files now carry the FULLwekanservice fromdocker-compose.yml(every documented environment variable and feature); the only differences are database-related (theferretdbservice replacesmongodb,MONGO_URLpoints at FerretDB,MONGO_OPLOG_URLis dropped and reactivity is polling, since FerretDB has no MongoDB change streams / replica-set oplog). -
Snap: one-time MongoDB → FerretDB v1 migration on upgrade, with live progress at ROOT_URL:
On first boot after upgrading an old MongoDB-based WeKan snap,
mongodb-controlhands off to a newmigration-controlbefore starting mongod. It opens the existing MongoDB data with the right bundled mongod — mongod 7 for MongoDB 7 data (all arches, including the arm64 snaps already on newest WeKan), or a bundled old mongod 3.2 (amd64 only) for WeKan 6.09 / MongoDB 3.2 data — starts a temporary FerretDB v1 (SQLite), and runs the migrator, which moves text data to FerretDB and both CollectionFS GridFS and Meteor-Files GridFS attachments+avatars to the filesystem, shows a live progress counter at ROOT_URL, checkpoints to$SNAP_COMMON(resumable), and compacts the old MongoDB when done. It then switches the snap todatabase=ferretdb. Idempotent, resumable, never deletes the source data, only switches on success. (The amd64 6.09/MongoDB-3.2 path needs testing on real 6.09 data; the arm64/MongoDB-7 path uses the already-bundled mongod 7.) -
Snap: bundle migratemongo (MongoDB 3.2 binaries + old libraries + AVX wrappers) to read 6.09 data:
Per docs/Backup/Backup.md and https://github.com/wekan/migratemongo, running the old MongoDB tools/server in the snap needs
LC_ALL=Cand their libraries onLD_LIBRARY_PATH($SNAP/lib/<arch>-linux-gnu), and the 2016 MongoDB 3.2 binaries additionally need old libraries (libssl/libcrypto.so.1.0.0,libpng12,libexpat) that modern bases lack. A newmigratemongosnapcraft part (amd64) stages https://github.com/wekan/migratemongo at$SNAP/migratemongo(its MongoDB 3.2bin/, the oldlib/x86_64-linux-gnu/, and theavx/QEMU wrappers) — also filling in the$SNAP/migratemongo/avxpath thatmongodb-control/-backup/-restorealready referenced but was never bundled.migration-controlnow reads the amd64 6.09 MongoDB 3.2 data with that mongod (oldLD_LIBRARY_PATH) and the legacymongoshell (mongosh cannot talk to 3.2). -
The bundled Node MongoDB driver can’t connect to a 3.2 server, and no single driver version spans 3.2 and MongoDB 7 / FerretDB — so rather than aliasing an EOL Node driver into
package.json, the amd64 6.09/3.2 case uses the migratemongo CLI:migration-controldumps thewekandatabase with the old migratemongomongodump(MongoDB 3.2, old libraries), then loads it into a fresh temporary mongod 7 with the snap’s modernmongorestore. The existing driver-based migrator then reads that mongod 7 exactly like the arm64 MongoDB-7 case (both GridFS types → filesystem, text → FerretDB v1 SQLite); the MongoDB-7 path connects the driver directly, unchanged. Also fixes a real bug: the standalone migrator in$SNAP/bincould not resolve itsmongodb/bsonimports —NODE_PATHnow points at the WeKan bundle’snode_modules. -
Refines the snap migration to the intended design: a MongoDB 7 database works with newest WeKan as-is and is not migrated; only the old 6.09 / MongoDB 3.2 data is.
migration-controlnow checks whether mongod 7 can open the data — if so it keeps MongoDB; otherwise it migrates the 3.x data. It reads it with the legacymongoexportCLI (the Node driver can’t talk to 3.2) and inserts into FerretDB with the Node driver — text streamed directly, GridFS attachments+avatars reassembled per-file straight tofiles/attachments/files/avatars— with no mongodump/mongorestore and no intermediate MongoDB 7 (which the earlier commit used). FerretDB’s SQLite now lives at<files>/db, next to attachments/avatars (thefiles/<name>layout), across the snap, offline launchers, Docker entrypoint and the v1-sqlite compose. -
A new “Database migration” section in Admin Panel / Attachments with two buttons: migrate text-based data (everything except attachments/avatars, which stay on the filesystem) to FerretDB v1 (SQLite) or back to MongoDB. WeKan is connected to one database at a time, so the server opens a second driver connection to the OTHER database (both speak the MongoDB wire protocol) and copies the text collections into it, upserting by
_id(idempotent). The target isWEKAN_FERRETDB_URL(defaultmongodb://127.0.0.1:27018/wekan) orWEKAN_MONGODB_URL(default:27019); both must be running. Progress is shown live; afterwards pointMONGO_URLat the other database and restart (Snap:snap set wekan database=ferretdb/=mongodb). Admin-only. -
Admin Panel / Attachments / Backup: scheduled backups streamed to storage, restore + list:
A new Backup section in Admin Panel / Attachments. Select any of Attachments, Avatars, Data (all text-based collections that are not attachments/avatars) and a storage (filesystem, S3/MinIO, Azure, GCS). “Backup now” streams the
.zipdirectly to the selected storage — no temp file, no extra disk — asbackup/YYYY/MM/DD/HH_MM_SS/backup.zipcontainingYYYY_MM_DD-HH_MM_SS/{attachments,avatars,data/<collection>.json}(filesystem pipes to the file; S3 uses@aws-sdk/lib-storagestreaming, AzureuploadStream, GCScreateWriteStream, with the cloud credentials from the storage tabs). A scheduler (off/daily/weekly/monthly + time/day) runs backups via synced-cron. List backups shows a table (storage, datetime, path); pick one and Restore with “Add missing data only” or “Replace all data”. Admin-only. (Cloud upload and restore are not exercised end-to-end yet; jszip assembles the whole zip, so very large attachment sets use notable memory.) -
Backup: switch from jszip to archiver+unzipper for low-memory streaming:
Follow-up to the Backup section above: it no longer holds whole files or the whole zip in memory. The backup
.zipis written with archiver, streaming each attachment/avatar straight from disk and each text collection a document at a time from a Mongo cursor as NDJSON, piped directly to the destination (filesystem or S3/Azure/GCS streaming upload). Restore uses unzipper: each file entry is piped to disk and each NDJSON data entry is applied line-by-line in 200-doc batches. A board with thousands of cards or a 5 GB attachment now backs up and restores with flat memory. -
Stream board exports (JSON, CSV/TSV, Excel) with bounded memory:
The board export routes used to buffer the whole board in memory: the JSON export built one object with every card, comment, activity, checklist and base64 attachment; CSV called that same builder; Excel additionally loaded data it never renders and did O(n²)
find()lookups. On large boards this peaked at gigabytes and could exceed V8’s max string length. Now the JSON export writes the document straight to the response a card at a time from raw cursors (attachments base64-encoded in aligned chunks), CSV streams one row per card keeping only the small lookup tables in memory, and Excel uses the exceljs streamingWorkbookWriter, committing each row and resolving card titles via an id→title map. Peak memory stays flat regardless of board size, and the JSON output is unchanged so import round-trips. -
Export board to HTML .zip: stream to disk and include every card:
The HTML export cloned the live DOM and built the whole
.zipas an in-memory blob — but infinite scroll keeps only ~10 cards per list in the DOM, so most cards were missing, and the archive was buffered whole in browser RAM. Every list’s card limit is now lifted so the entire board renders before the snapshot, and the zip is written with JSZip’sgenerateInternalStreampiped straight to the chosen file via the File System Access API, chunk by chunk with backpressure (browsers without the API fall back to the previous blob download). -
Lazy card loading for very large boards: CARDS_LOADING=all|lazy + Admin Panel / Features:
A board’s
boardpublication normally ships every non-archived card (with full fields, comments, attachments and checklists) into each viewer’s minimongo. The list rendering is already infinite-scrolled (~10 cards per list in the DOM), but the whole dataset still crosses the wire and sits in browser memory, so a board with thousands of cards is heavy for every viewer.A new
CARDS_LOADINGmode (alldefault, orlazy) makes each list load only the cards it is about to render. In lazy mode the board publication ships no cards; instead each list/swimlane subscribes to a windowed publication (boardCardsWindow) for just its visible window — growing as you scroll — plus a reactive total count (boardListCardCount) so it knows when more remain. The window selector is ANDed with a server-forced board scope and refuses$where. This is set by theCARDS_LOADINGenv var (exposed indocker-compose*.yml, the bundlestart-wekan.sh/.bat, andsnap set wekan cards-loading=lazy) and also at runtime in a new Admin Panel / Features section — the intended home for optional / performance / future tier-gated capabilities. (client + Features, platform env)Lazy mode is opt-in and experimental: card counters and WIP limits are accurate (they read a server count for the list’s exact selector), but the Calendar/Table/Gantt views and multi-select currently reflect only the cards loaded so far, and open boards must be reloaded after switching modes. Default
allis unchanged. (accurate counters + WIP) -
Fix Transifex push: remove duplicate database-migration i18n keys:
en.i18n.jsonhaddatabase-migrationanddatabase-migration-descriptiondefined twice, so the Transifex source push failed with “Duplicate string key”. The later, unused copies (“Database Migration” / “Updating database structure…”) were removed, keeping the values the Admin Panel / Attachments migration UI actually renders. JSON now parses with unique keys. -
Finnish translations for the newest features:
Translated the remaining English strings of the Upcoming features into Finnish (
fi.i18n.json), matching existing terminology: Admin Panel / Version (database type, FerretDB/MongoDB version + commit, reactivity mode), Attachments / Database migration, Features (card loading all/lazy), and Backup (schedule, storage, restore, list). 44 keys; product names/acronyms kept as-is. -
Unit + negative tests for the newest features:
Extract the pure, security-/correctness-critical logic of the recent features into Meteor-free
models/lib/*modules (shared by production and tests) and add plain-Node tests with positive and negative cases, wired intotest:unit:node: the windowed-card publication’s$whereselector safety,CARDS_LOADINGmode resolution + window-count id, the JSON export’s streaming base64 chunker, and the backup files-root + schedule-text helpers. 41 assertions, all passing. -
docs: Design/Multiverse/Alternative-Architectures.md:
Document which CPU architectures WeKan can run on and why (the limit is Node.js, not FerretDB): the Node 24 / MongoDB / FerretDB v1 matrix, why armhf/armv7l/i386 are unsupported (Node dropped 32-bit) and loong64 is not buildable in CI (no QEMU emulation / base image), the JavaScript-engine alternatives (Deno/Bun cover fewer arches; QuickJS/JSC/SpiderMonkey/JVM run on 32-bit but cannot run Meteor), and server-rewrite options (Go recommended, QuickJS niche, Tcl/Tk a poor fit).
-
Fix backup build: archiver@8 (ESM) + @aws-sdk/lib-storage dependency:
The Meteor/rspack build failed on
server/methods/backup.js:archiver@8is now pure ESM with no default export orarchiver('zip', …)factory (it exports classes), and the S3 streaming upload used@aws-sdk/lib-storage, which was not a dependency. Useimport { ZipArchive } from 'archiver'/new ZipArchive({ zlib: { level: 6 } }), and add@aws-sdk/lib-storage(pinned to~3.1073.0to match the installed@aws-sdk/client-s3).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.