This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+2 more
ReleasePort's take
Light signalWekan v9.81 introduces several UI enhancements and API improvements, including alphabetically sorted link‑card dropdowns, validated color editing via REST, native color pickers with contrast calculation, and expanded rule date granularity.
Why it matters: These changes improve usability (sorted menus, clearer icons) and reliability (validated API payloads, consistent webhook triggers), affecting all board users and integrators; no migration deadline or CVE is involved.
Summary
AI summaryUpdates https://github.com/wekan/wekan/commit/b07c4d0a7bed0f03ba2d7d1ad1f030e7edf3dfff, https://github.com/wekan/wekan/commit/7ce71dbb2e1eae5159503eefac7e6f349dfb59c8, and https://github.com/wekan/wekan/commit/30f36a7f319675bd3aac7a4ba0700427000726b9 across a mixed release.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Feature | Medium |
Link-card popup's Cards dropdown is now sorted alphabetically by card title. Link-card popup's Cards dropdown is now sorted alphabetically by card title. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Feature | Medium |
Lists can be edited via REST API `PUT /api/boards/:boardId/lists/:listId` with validated color values. Lists can be edited via REST API `PUT /api/boards/:boardId/lists/:listId` with validated color values. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Feature | Medium |
Color pickers now include a native `<input type="color">` wheel and compute readable text contrast. Color pickers now include a native `<input type="color">` wheel and compute readable text contrast. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Feature | Medium |
Rules can set a date field with custom time units (minute, hour, day, week, month). Rules can set a date field with custom time units (minute, hour, day, week, month). Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Users are auto‑added to organizations matching their email domain on sign‑up. Users are auto‑added to organizations matching their email domain on sign‑up. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Activities show/hide control now uses clear eye/eye‑slash icons with proper tooltips. Activities show/hide control now uses clear eye/eye‑slash icons with proper tooltips. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Outgoing webhooks now include the label name on add/remove label events. Outgoing webhooks now include the label name on add/remove label events. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Editing a card description now triggers outgoing webhooks. Editing a card description now triggers outgoing webhooks. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Rules Workflow view is now fully translatable via i18n. Rules Workflow view is now fully translatable via i18n. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Deleting a board rule no longer fails with "Access denied [403]". Deleting a board rule no longer fails with "Access denied [403]". Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
REST API endpoint PUT /api/boards/:boardId/labels now always returns JSON responses and validates input. REST API endpoint PUT /api/boards/:boardId/labels now always returns JSON responses and validates input. Source: granite4.1:30b@2026-07-16-audit Confidence: low |
— |
| Bugfix | Low |
CSV export no longer crashes on boards with dangling references; missing data yields blank cells. CSV export no longer crashes on boards with dangling references; missing data yields blank cells. Source: granite4.1:30b@2026-07-16-audit Confidence: low |
— |
| Bugfix | Low |
Calendar view now honors active board filters (member, assignee, due date, label, custom field). Calendar view now honors active board filters (member, assignee, due date, label, custom field). Source: granite4.1:30b@2026-07-16-audit Confidence: low |
— |
| Bugfix | Low |
All Boards "Custom (drag order)" drop operation now persists the reorder of boards. All Boards "Custom (drag order)" drop operation now persists the reorder of boards. Source: granite4.1:30b@2026-07-16-audit Confidence: low |
— |
| Bugfix | Low |
Cards on a deleted swimlane are now visible in swimlane view by appearing in the first swimlane. Cards on a deleted swimlane are now visible in swimlane view by appearing in the first swimlane. Source: granite4.1:30b@2026-07-16-audit Confidence: low |
— |
Full changelog
v9.81 2026-07-09 WeKan ® release
This release adds the following new features:
- Feature #5394: the Link-card popup's Cards dropdown is now sorted alphabetically:
In the "Link to this card" popup, the Cards pull-down list is now sorted
alphabetically by card title (case-insensitive, locale/numeric aware)
instead of board sort order, so a card can be found on boards with many
cards. Thanks to xet7. - Feature #5396: edit Lists (title, color) via the REST API + api.py commands:
Lists can now be edited through the REST API like cards can. The endpoint
PUT /api/boards/:boardId/lists/:listIdalready acceptedtitle,color,
starredandwipLimit, but the color was stored unvalidated; it now
validates the color withnormalizeListColor(a named palette color or a
custom#rrggbbhex) and rejects an unknown color with a clear 400 instead
of silently storing None. The pure field/validation logic lives in a
Meteor-free helpermodels/lib/listApiUpdate.jsand is unit tested in
tests/listApiUpdate.test.cjs. Theapi.pyreference CLI gains two new
commands mirroringeditcard/editcardcolor:editlist BOARDID LISTID NEWLISTTITLEandeditlistcolor BOARDID LISTID COLOR. Thanks to C0rn3j and xet7. - Feature #5514: custom color-wheel (RGB/hex) picker with automatic readable text contrast:
The color pickers that previously offered only a fixed set of named colors now also include a native
color wheel (<input type="color">), so any#rrggbbcolor can be chosen. The wheel was added
alongside the existing swatches for card labels ("categories"), swimlanes ("tabs"), lists and cards.
The schema color fields accept a custom hex in addition to the named palette, and existing named-color
data keeps working; a stored hex is rendered with an inlinebackground-colorinstead of the named
CSS class. A new pure, Meteor-free helpermodels/lib/contrastColor.jscomputes a readable text color
from sRGB relative luminance (white text on dark backgrounds, black on light), maps the named palette
to hex, and validates/normalizes hex; it is applied as an inline text color wherever a chosen color is
a background behind text (label chips, swimlane / list headers, minicard, card details header), so text
stays readable on any color. Covered bytests/contrastColor.test.cjs(20 assertions pass).
Thanks to Ruyeex and xet7. - Feature #5621: Rules can set a date field to a custom time (value + minute/hour/day/week/month later):
The Rules "Set date relative to now" action previously only offset a date field (Start / Due / End /
Received) by a whole number of DAYS. It now has a unit selector, so a rule can set a date field to
now +<value><unit> later, where the unit is minute(s) / hour(s) / day(s) / week(s) / month(s);
negative values move the date earlier. Months use a real calendar-month add (e.g. keeping the same
day-of-month) rather than a fixed 30-day approximation. Existing rules created before this change
have no stored unit and keep working exactly as before (no unit ⇒ days), so the change is fully
backward compatible. The offset math lives in a new pure, Meteor-free helper
models/lib/relativeDateOffset.jsused byserver/rulesHelper.jsand covered by
tests/relativeDateOffset.test.cjs(15 assertions pass). Note that the related "overdue" Rules
trigger and the "set date field to now" action from the same feature request already existed in an
earlier release; this change adds only the missing custom-time unit selector. Thanks to xet7.
and fixes the following bugs:
- Fix #5351: users are auto-added to organizations matching their email domain on sign-up: the Organization setting "Automatically add users with the domain name" (
org.orgAutoAddUsersWithDomainName) could be configured, but nothing at sign-up ever read it, so a new user whose email domain matched an organization was never added to it; theAccounts.onCreateUserhook now, on every non-admin sign-up path (password registration, LDAP, invitation code, new OIDC user), adds the user to each organization whose configured domain exactly matches the domain part of their email (case-insensitive, exact — a subdomain does not match and an empty org domain matches nobody), using the same{ orgId, orgDisplayName }membership shape used everywhere else and never duplicating an existing membership, with the matching decision extracted into a Meteor-free, unit-testedorgsToAutoAddForEmailhelper.
Thanks to xet7. - Fix #5369: the Activities show/hide control is now a clear eye / eye-slash icon toggle: the Activities panel's show/hide control was a generic, unlabeled material toggle switch whose ON/OFF meaning was counterintuitive; it is replaced on the card details Activities panel with an eye / eye-slash icon toggle (open eye = activities shown, crossed eye = activities hidden) that mirrors the login/register password-visibility toggle, so the icon reflects the current visibility, clicking flips it, and the tooltip states the action (Show activities / Hide activities). For consistency the board sidebar Activities toggle, which drives the same showActivities state, was switched from the check / empty-square icon to the same eye / eye-slash toggle. The underlying show/hide setting and its persistence are unchanged.
Thanks to xet7. - Fix #5442: outgoing webhooks now include the label name on add/remove label: the addedLabel/removedLabel outgoing webhook (and notification) text showed a bare, generic "label" with no name, because labels are embedded in the board document but the Activities
label()helper looked the label id up in the Cards collection and always returned undefined, so the__label__token was never filled; the hook now resolves the label from the already-loaded board viagetLabelByIdand a pure, unit-testedlabelDisplayNamehelper (name, then color for a nameless label as shown in the UI, then the id), so the webhook always carries the label's display name.
Thanks to xet7. - Fix #5482: adding/editing a card description now triggers outgoing webhooks: outgoing webhooks fire only when an operation logs an activity (the Activities.after.insert hook posts to the board's webhooks), but changing a card's description created no activity — unlike title/date changes — so no webhook was sent; the Cards before.update hook now logs an
a-changedDescriptionactivity on first-time set and later edits (but not on no-op / empty-to-empty saves), so the existing webhook hook fires, with a Meteor-free unit-testeddescriptionChangedhelper.
Thanks to xet7. - Fix: the Rules Workflow view is now fully translatable via i18n: the Rules Workflow view rendered its trigger/action palette chips ("Card is created", "Move card to top", "Set received date to now", etc.) as hardcoded English regardless of the UI language, while the rest of the page was translated; each palette entry now carries an i18n key translated at render time via TAPi18n.__ (reusing existing rule keys where they fit, plus new r-w-* keys for workflow-only labels), and the slot clear tooltip now uses the existing r-remove key, so the whole view follows the selected language.
Thanks to xet7. - Fix: deleting a board rule no longer fails with "Access denied [403]": deleting a rule ran three separate client-side Collection.remove() calls (Rules + Triggers + Actions), each gated by a per-collection allow() rule that resolved the board from that document's own boardId; when a trigger/action document had no resolvable boardId (legacy docs, or docs not published to the client) the board came back null,
allowIsBoardAdminreturned false, and Meteor rejected the mutation with 403 "Access denied" — so the delete failed even for a legitimate board admin. Rule deletion now goes through a new server methodrules.deleteRulethat authorizes once (active board admin or site admin) and removes the rule, its trigger and its action server-side, bypassing the brittle client allow/deny; the permission decision is a Meteor-free, unit-tested helper and no permission is loosened.
Thanks to xet7. - Fix #5510: adding a board label via the REST API no longer errors/hangs: PUT /api/boards/:boardId/labels only sent a response when the body had a
labelkey, so a body without one hung until the client timed out, and a bare-stringlabelpushed a schema-invalid label and returned 200; the handler now always returns JSON (2xx on success, 4xx on bad input, real error status otherwise) via a pure, unit-tested input helper.
Thanks to xet7. - Fix #5604: CSV export no longer crashes on boards with dangling references: exporting a large/old board to CSV failed with "Couldn't download - Network issue" because Exporter.buildCsv read
.title/.username/.namedirectly on the result of looking up a card's deleted list/swimlane/owner/member/assignee/label/customField by id, throwing "Cannot read property 'title' of undefined"; the per-card row builder is now a null-safe helper that emits a blank cell for missing references while keeping identical output for well-formed cards.
Thanks to xet7. - Fix #5656: the Calendar view now honors the active board filters: the Calendar view queried cards by board and date only and ignored the active Filter sidebar (member / assignee / due-date / label / custom-field), so it showed every card in the interval unlike the Board / Lists / Swimlanes views; it now ANDs the Filter selector into its query and refetches when the filter changes.
Thanks to kerier and xet7. - Fix #6442: All Boards "Custom (drag order)" — drop now persists the reorder:
Follow-up to #6439, which restored the drag preview but left the drop a no-op: dragging a board on
the All Boards page in Custom (drag order) mode showed the dashed-border preview, but releasing it
snapped the board back without reordering. The drop handler built the current on-screen order from
el.classList[0]of each.js-board, but the item isli.js-board(class="{{_id}} …")and Jade
emits the literaljs-boardclass FIRST, soclassList[0]was the string"js-board"for every
board — never the board_id. The ordered ids were therefore['js-board','js-board',…], the
(correct, unit-tested)computeReorderedSortIndexhelper could not find the dragged/target ids
among them, returnednull, and nothing was written toprofile.boardSortIndex; the preview still
worked because it is driven by thedragoverCSS class, independent of the id. The fix reads each
board's_idfrom its Blaze data context (the same sourcedragstartuses viathis._id) instead
of the literal class, so the real display order reaches the reorder helper and the drop persists.
Guarded by a new case intests/boardSortReorder.test.cjs(the wrong-class extraction yields no
mapping; real ids reorder).
Thanks to jullbo and xet7. - Fix #6443: cards on a deleted swimlane are invisible in swimlane view:
On some old boards a swimlane was deleted while its cards kept the now-danglingswimlaneId(an
"orphaned" card), so those cards showed no content in swimlane mode even though they worked in list
mode. Cards with no swimlane at all (null/''/ missing) already appear in every swimlane, but
an orphaned card matched no existing swimlane and so was visible in NO swimlane (while list view,
which applies no swimlane scope, still showed it) — exactly the reported symptom. The fix mirrors
the existing orphaned-list fallback (Swimlanes.orphanedSwimlaneLists, which surfaces orphaned
lists in the first swimlane) for cards: when a list's cards are fetched for the board's FIRST
swimlane, the swimlane-membership clause becomes a single{ swimlaneId: { $nin: <otherSwimlaneIds> } }
(everything not owned by another existing swimlane: own id,null/'', missing, or orphaned). It
stays a single field clause with no second$or, so the #6441 board-wide label filter still holds,
and orphaned cards appear once — in the first swimlane — without a database migration. Threaded
through the puremodels/lib/swimlaneFilter.jshelpers, the in-memoryfilterCardsByListAndSwimlane,
a newList.orphanedCardsSwimlaneIdshelper and thecards()/cardsUnfiltered()/allCards()model
methods plus thelistBodycardsWithLimitrender helper. Covered by new cases in
tests/swimlaneFilter.test.cjs(18 assertions pass; the #6441 regression guards stay intact).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
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.
Beta — feedback welcome: [email protected]