This release includes 3 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+2 more
Affected surfaces
Summary
AI summaryBroad release touches SQLite, https://github.com/wekan/wekan/commit/0360ca1583b4b17b465ded20c4fd7560004aee47, https://github.com/wekan/FerretDB, and https://github.com/wekan/wekan/commit/598aa8dfa1cc406243e0db681573093d9db7783f.
Full changelog
v9.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. Added
tests/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 shared
TAPi18n.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 leaked
Tracker.autorunin the.getLanguagereactive test (the cross-test hazard). -
The extra-arch bundle build ran
node:24-slimunder QEMU, but the official
node: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 with
WEKAN_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 new
ferretdbservice 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/betathen
snap 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 singleferretdb.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 newest
ferretdb.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 from
release-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 to
docker-compose-ferretdb-v2-postgresql.yml, and a new
docker-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-control
hands 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 needsLC_ALL=Cand their libraries on
LD_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 that
mongodb-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 intopackage.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/bson
imports —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 is
WEKAN_FERRETDB_URL(defaultmongodb://127.0.0.1:27018/wekan) or
WEKAN_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 — as
backup/YYYY/MM/DD/HH_MM_SS/backup.zipcontaining
YYYY_MM_DD-HH_MM_SS/{attachments,avatars,data/<collection>.json}(filesystem pipes
to the file; S3 uses@aws-sdk/lib-storagestreaming, AzureuploadStream, GCS
createWriteStream, 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-description
defined 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-freemodels/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.
Breaking Changes
- Scheduled backup code now requires async/await; existing callback‑style implementations will fail on Meteor 2.x.
- Snap migrations from MongoDB 3.2 are performed automatically on first boot after upgrade; previous manual migration steps are no longer needed.
- Lazy card loading (`CARDS_LOADING=lazy`) changes behavior of views that rely on full board data (Calendar, Table, Gantt) – they now show only loaded cards.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About wekan
The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . New English strings for new features at imports/i18n/data/en.i18n.json . Non-English translations at https://app.transifex.com/wekan/wekan only.
Related context
Related tools
Beta — feedback welcome: [email protected]