Skip to content

wekan

v9.84 Breaking

This release includes 3 breaking changes for platform teams planning a safe upgrade.

Published 16d Productivity & Wikis
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

docker javascript kanban meteor real-time sandstorm
+2 more
snapcraft wekan

Affected surfaces

auth

Summary

AI summary

Broad 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 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:

    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.

  • Snap: choose MongoDB or FerretDB v1 with "snap set wekan database=ferretdb"; FerretDB default for new installs; disable mongosh telemetry:

    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: 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).

  • Standalone ferretdb.zip for all platforms, and rebuild-all.yml uses the prebuilt one from wekan/FerretDB releases:

    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.

  • 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 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:

    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.

  • Self-contained release bundles: bundle Node.js + FerretDB + start-wekan.{sh,bat}:

    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):

    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.

  • 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.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.

  • Snap: migrate the Caddyfile from Caddy v1 to Caddy v2 format on upgrade:

    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, gzipencode 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.

  • 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.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).

  • 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 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.)

  • 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=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).

  • Snap migration: read MongoDB 3.2 via migratemongo CLI (dump → restore into mongod 7), fix migrator NODE_PATH:

    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.

  • Snap migration: only MongoDB 3 migrates (mongo CLI read + Node driver insert), FerretDB SQLite at files/db:

    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.

  • Admin Panel / Attachments: migrate text data between MongoDB and FerretDB v1 (SQLite), both directions:

    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.

  • 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
    .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.)

  • 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 .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.

  • 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
    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.

  • Export board to HTML .zip: stream to disk and include every card:

    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).

  • Lazy card loading for very large boards: CARDS_LOADING=all|lazy + Admin Panel / Features:

    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)

  • Fix Transifex push: remove duplicate database-migration i18n keys:

    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.

  • 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 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:

    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@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.

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

Track wekan

Get notified when new releases ship.

Sign up free

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.

All releases →

Related context

Beta — feedback welcome: [email protected]