| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| wekan-9.84-riscv64.zip | 2026-07-11 | 263.5 MB | |
| wekan-9.84-s390x.zip | 2026-07-11 | 264.0 MB | |
| wekan-9.84-ppc64le.zip | 2026-07-11 | 266.1 MB | |
| wekan-9.84-win64.zip | 2026-07-11 | 347.3 MB | |
| wekan-9.84-mac-arm64.zip | 2026-07-11 | 258.8 MB | |
| wekan-9.84-amd64.zip | 2026-07-11 | 255.7 MB | |
| wekan-9.84-arm64.zip | 2026-07-11 | 262.0 MB | |
| README.md | 2026-07-11 | 26.8 kB | |
| v9.84 source code.tar.gz | 2026-07-11 | 31.2 MB | |
| v9.84 source code.zip | 2026-07-11 | 32.2 MB | |
| Totals: 10 Items | 2.0 GB | 0 | |
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 logged findOne is not available on the server. Please use
findOneAsync() instead. and the cron silently never registered, so scheduled
backups did not run. registerCron() is now async and uses findOneAsync,
the getBackupSchedule/saveBackupSchedule methods use findOneAsync/
upsertAsync, and every registerCron() 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 the test:unit:node suite.
- Snap: show the correct writable path for parallel installs in backup instructions:
The backup/migration help text printed by wekan.help hardcoded
/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 .loadLanguage suite stubbed addResourceBundle on the shared
TAPi18n.i18n singleton and asserted the call count. Because loadLanguage()
reads this.i18n late 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
reported expected addResourceBundle to be called once. The tests now assert
on observable i18next state (hasResourceBundle/getResourceBundle under the
normalised toI18nCode), which is instance-agnostic and deterministic and
mirrors the reliable .setLanguage suite. Also stopped a leaked
Tracker.autorun in the .getLanguage reactive test (the cross-test hazard).
- Fix rebuild-all.yml s390x build; add bundled FerretDB v1 for ppc64le, s390x, riscv64 and to the Docker image (github.com):
The extra-arch bundle build ran node:24-slim under QEMU, but the official
node:24 image publishes no linux/s390x manifest (only amd64/arm64/ppc64le),
so the s390x leg failed with "no matching manifest for linux/s390x". The
emulated native-module rebuild now runs on an ubuntu:26.04 base (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 .zip next to main.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, plus DO_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 database setting (mongodb or ferretdb). A new
ferretdb service runs FerretDB v1 (SQLite) on the same port MongoDB would
use, so MONGO_URL is unchanged; only one database runs at a time, and the
configure hook stops one and starts the other when the setting changes. Switch
with snap set wekan database=ferretdb (or back with database=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=ferretdb flag (snapd has no custom install options); use
the two-step snap install wekan --channel=latest/beta then
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 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).
- Standalone ferretdb.zip for all platforms, and rebuild-all.yml uses the prebuilt one from wekan/FerretDB releases (github.com):
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 newest
ferretdb.zip from 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.
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 with ENOENT ... 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 the build-chunks/ prefix, but
client/00-startup.js set the sub-path public path to <sub-path>/build-chunks/,
so rspack appended a second build-chunks/. It now sets the public path to just
<sub-path>/ and lets rspack add build-chunks/ itself.
- Add snapcraft-core24.yaml so the newest WeKan can be published to the Snap Stable channel (github.com):
The main snapcraft.yaml uses base: core26, which (until core26 is released)
needs build-base: devel + grade: devel, so it can only go to Snap Beta/Edge.
snapcraft-core24.yaml builds the SAME newest WeKan (Meteor 3.5, Node.js 24,
FerretDB v1, MongoDB 7, Caddy 2) on base: 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.
Each wekan-<version>-<arch>.zip is now fully offline. Its bundle/ directory
contains the WeKan server, a Node.js binary for that platform, a FerretDB v1
(SQLite) binary, and a start-wekan.sh (start-wekan.bat on Windows) that by
default runs WeKan on the bundled Node against the bundled FerretDB SQLite,
storing data and attachments/avatars on the filesystem under WRITABLE_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.
- Standalone sandstorm.yml workflow to build + attach the .spk (Sandstorm removed from release-all.yml) (github.com):
Sandstorm packaging (mirroring releases/release-sandstorm.sh +
install-sandstorm.sh: installs Meteor, meteor-spk 0.6.0 and a dev Sandstorm,
runs meteor-spk pack) is not tested well enough yet, so it no longer runs
as part of a full release — the build-sandstorm job was removed from
release-all.yml. Instead a separate, manually-triggered sandstorm.yml builds
ONLY the .spk and uploads it as wekan-sandstorm-YYYY_MM_DD-HH_MM_SS.spk to 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 .spk needs the app private
key via the SANDSTORM_KEYRING secret; spk publish / scp upload stay manual.
The MongoDB → FerretDB / GridFS → filesystem migrator (used by the Snap and
Sandstorm migrations) now checkpoints progress to
$WRITABLE_PATH/migration-progress.json after 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 running compact on 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.
When upgrading from an old WeKan snap, $SNAP_COMMON/Caddyfile may still be in
Caddy v1 syntax, which Caddy 2 cannot parse (caddy would fail to start).
caddy-control now runs a converter before caddy run when 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 v1 websocket/transparent presets Caddy 2
does by default, gzip → encode gzip, strip the http:// scheme), validates
with caddy adapt, and only keeps a valid result — falling back to the shipped
Caddy 2 template otherwise, so caddy always starts with valid config.
docker-compose-ferretdb.yml (FerretDB 2 + PostgreSQL) is renamed to
docker-compose-ferretdb-v2-postgresql.yml, and a new
docker-compose-ferretdb-v1-sqlite.yml runs 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 FULL wekan service from docker-compose.yml
(every documented environment variable and feature); the only differences are
database-related (the ferretdb service replaces mongodb, MONGO_URL points
at FerretDB, MONGO_OPLOG_URL is dropped and reactivity is polling, since
FerretDB has no MongoDB change streams / replica-set oplog).
On first boot after upgrading an old MongoDB-based WeKan snap, mongodb-control
hands off to a new migration-control before 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 to database=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.)
Per docs/Backup/Backup.md and https://github.com/wekan/migratemongo, running the
old MongoDB tools/server in the snap needs LC_ALL=C and 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 new migratemongo snapcraft
part (amd64) stages https://github.com/wekan/migratemongo at $SNAP/migratemongo
(its MongoDB 3.2 bin/, the old lib/x86_64-linux-gnu/, and the avx/ QEMU
wrappers) — also filling in the $SNAP/migratemongo/avx path that
mongodb-control/-backup/-restore already referenced but was never bundled.
migration-control now reads the amd64 6.09 MongoDB 3.2 data with that mongod
(old LD_LIBRARY_PATH) and the legacy mongo shell (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-control dumps the wekan database with the old
migratemongo mongodump (MongoDB 3.2, old libraries), then loads it into a fresh
temporary mongod 7 with the snap's modern mongorestore. 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/bin could not resolve its mongodb/bson
imports — NODE_PATH now points at the WeKan bundle's node_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-control now 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
legacy mongoexport CLI (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 to files/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
(the files/<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 (default mongodb://127.0.0.1:27018/wekan) or
WEKAN_MONGODB_URL (default :27019); both must be running. Progress is shown
live; afterwards point MONGO_URL at the other database and restart (Snap:
snap set wekan database=ferretdb / =mongodb). Admin-only.
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
.zip directly to the selected storage — no temp file, no extra disk — as
backup/YYYY/MM/DD/HH_MM_SS/backup.zip containing
YYYY_MM_DD-HH_MM_SS/{attachments,avatars,data/<collection>.json} (filesystem pipes
to the file; S3 uses @aws-sdk/lib-storage streaming, Azure uploadStream, 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.)
Follow-up to the Backup section above: it no longer holds whole files or the
whole zip in memory. The backup .zip is 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.
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
streaming WorkbookWriter, 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.
The HTML export cloned the live DOM and built the whole .zip as 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's generateInternalStream piped
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).
A board's board publication 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_LOADING mode (all default, or lazy) 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 the CARDS_LOADING env var (exposed in docker-compose*.yml,
the bundle start-wekan.sh/.bat, and snap 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
all is unchanged. (accurate counters + WIP)
en.i18n.json had database-migration and database-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.
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.
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 into test:unit:node:
the windowed-card publication's $where selector safety, CARDS_LOADING mode
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 (github.com):
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).
The Meteor/rspack build failed on server/methods/backup.js: archiver@8 is now
pure ESM with no default export or archiver('zip', …) factory (it exports
classes), and the S3 streaming upload used @aws-sdk/lib-storage, which was not
a dependency. Use import { ZipArchive } from 'archiver' /
new ZipArchive({ zlib: { level: 6 } }), and add @aws-sdk/lib-storage
(pinned to ~3.1073.0 to match the installed @aws-sdk/client-s3).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.