Repository navigation
v0.28.0
[0.28.0] - 2026-10-23
Headline: Change one thing without restating the rest: alter navigation, alter menu and alter consumed rest service edit a document in place, ALTER PAGE reaches every widget, and create or modify of a flow splices note, caption and error-handler changes instead of refusing them. Alongside it: Protected (add-on) modules and export level are understood by check and MDL, mxcli marketplace package exports a module as a .mpk, rewriting a page no longer deletes the widgets MDL cannot author, and the deprecated v1 BSON driver is gone, leaving mxcli on mongo-driver/v2 alone.
Added
alter consumed rest service— add, change and remove operations without restating the service (#1608) —alter consumed rest service M.Api add operation GetBooking ( … ), modify operation PostBooking (Timeout: 60), drop operation Old, set (BaseUrl: '…'). The operation body is CREATE's;modifywrites only the keys it lists; actions apply in order, sodrop operation X, add operation X (…)replaces X. It edits the stored document in place rather than rebuilding it, so an operation no action names is written back byte for byte — including what Studio Pro stored that MDL cannot say (an optional query parameter, a test value), which acreate or modifyrestating the service resets. Before this the statement did not parse (no viable alternative at input 'alterconsumedrest') and the only MDL route was acreate or modifythat deleted any operation it did not list.- Lint rule CONV020: an object created in a microflow and never committed, returned or passed on (#1518) —
$o = create Shop.Item (…)withoutcommitmakes an object in memory only; if the microflow then never commits it or hands it to anything that could, the object is discarded when the microflow ends, andcheck,mx checkand the build all pass. In the field an after-startup seed microflow logged "Seeded 20" and the table had 0 rows. CONV020 (warning) reports a create with commit No of a persistent entity in a microflow when nothing in the flow uses the variable except to read a member ($o/Cnt) or change it without commit. Any other mention — commit, change with commit, return, an argument to a microflow, Java/JavaScript action, workflow, mapping or page, a list, an association value, delete, rollback, or the bare variable in any expression — counts as passing it on. It is conservative by design: flow-insensitive (a commit on one branch or in an error handler counts), and nanoflows, non-persistent entities and System/Marketplace modules are not reported; anything it cannot classify keeps it quiet. Docs: "Built-in Rules" › CONV020 details. - QUAL004's entry-point prefixes and CUSTOM002's valid prefixes are
lint-config.yamloptions (#1509) — both lists were constants in the rule files, andmxcli initrewrites those files, so a project whose convention names microflowsTEST_,GET_orPRS_could not keep an edit and got one finding per such microflow (84 QUAL004 and 121 CUSTOM002 findings on the reporting project).rules.QUAL004.options.entry_point_prefixesandrules.CUSTOM002.options.valid_prefixestake a YAML list or comma-separated text and replace the built-in list; unset, the built-in list applies as before. Underneath, a YAML list or map under any rule'soptionsreached a Starlark rule asNone—get_optionconverted scalars only — so a list-valued option was dropped without a word; lists and maps now convert recursively, the newget_list_option(key, default)builtin reads a list of strings in either spelling, and an option value a rule cannot use is reported as a warning namingrules.<ID>.options.<key>(kept out of the score) while the rule runs on its default. QUAL004 still counts only callers in the model: a microflow called only fromtests/*.test.mdlis reported, and its prefix belongs inentry_point_prefixes. Docs: "Starlark Rules" › QUAL004 and CUSTOM002 options. layoutheaderand native layout parts — every Atlas layout copies whole —describe layout→create layoutof Atlas's Phone_ and Tablet_ layouts dropped their header bar (Forms$Header, printed as NOT re-executable), so pages bound to itsHeaderLeft/HeaderRightplaceholders failed CE1613 and a copy of Phone_Sidebar or Tablet_Sidebar failed CE0611 "A sidebar toggle button is required if a region can toggle".layoutheader header1 { region left { … } region right { … } }writes it. A native layout's copy silently lost its header placeholder, bottom bar and sidebar — pages bound toHeaderor a sidebar placeholder then failed CE1613; they areregion header { placeholder Header },showbottombar: trueandregion sidebar { … }, and the native content is written with the eight keys Studio Pro stores. Measured on a new 11.13.0 app: all 22 Atlas_Core layouts copied, with pages bound to the copied header and sidebar placeholders, build with 0 errors.sidebartoggle— Atlas's responsive sidebar toggle (Forms$SidebarToggleButton) in MDL (#1220) — copyingAtlas_Core.Atlas_Defaultwithdescribe layout→create layoutdropped the button that opens the sidebar on phones and tablets:describeprinted it as a NOT re-executable comment, andexecgave no warning.sidebartoggle sidebarToggle3 (Tooltip: 'Toggle Menu', ButtonStyle: Primary, Icon: 'Atlas_Core.Atlas_Filled.navigation-menu', Class: 'toggle-btn')takes an action button's caption, tooltip, style and icon, andRenderMode: Link; it has no action. The written widget has the keys of Atlas's own, including its caption underCaptionTemplaterather than theCaptionkey the vendored metamodel bound. A scroll container region'sToggleMode(ToggleMode: 'ShrinkContentInitiallyClosed'), which the toggle opens, is described and written too — without it the copy failed CE0628.mxcli marketplace package— export a module as a.mpk(#1382) —mxcli marketplace package MyModule -p app.mpr [-o dir] [--all-libs] [--exclude REGEX] [--exclude-managed-dependencies] [--json]is the reverse ofmarketplace install --file: it wraps Mendix's ownmx create-module-package(resolved for the project's Mendix version), so the package is what Studio Pro's Export module package makes, and it installs back withinstall --file. By default only the module's own requireduserlib/JARs go in (mx -l; measured 12.5 KB against 24 MB with Business Events installed);--all-libsbundles every JAR.-lfilters only JARs — mx still exports the app's wholeresources/folder (measured: another module'sresources/EventLog/came along) — so after the export mxcli opens the package, warns on stderr about eachresources/<X>/,javasource/<x>/orthemesource/<x>/file of another module, and prints the--excludepattern that drops them in the one form mx accepts (^resources.EventLog.; a character class or backslash is rejected by mx with exit 1 and a usage message, and an exit 0 that wrote no package is reported as an error too). A module name that is not in the project lists the project's modules instead of failing with mx's exit 3, and mx's exit codes 1–4 are reported with what they mean and what mx printed.mx create-module-packagewrites into the project it is given — measured on a blank 11.12.5 app:theme-cache/web/theme.compiled.cssand its map rewritten,deployment/sass/main.scssadded, the module'sthemesource/folder created, model files untouched — so it runs on a temporary copy, asdocker checkdoes; a before/after listing of the project (sizes and modification times) was identical. Themxthat runs must be the project's Mendix version:ResolveMxForVersionprefers anmxon PATH and falls back to any cached one, so mxcli reads the version of themxit found (itsmx.deps.json, else its version-named folder) and refuses a different major.minor.patch, or one it cannot identify, naming both versions and the fix (mxcli setup mxbuild --version X);--mxbuild-pathis the override, with a warning on stderr. mx's own output on a successful export goes to stderr, cancelling the command stops mx, and an output directory inside the project is not copied into the temporary copy. Round trip on 11.12.5: export,install --fileinto a second fresh app,mx check— 0 errors.mxcli search --exclude-marketplaceleaves out hits inside Marketplace modules (#1477) — an audit of the app's own text (a leftoverTODO, a word that must not ship) reported a vendor module's text beside the project's own. The flag drops hits whose module was downloaded from the Marketplace, including the module's own documentation and domain model documentation; it is off by default, becausesearchis read-only and the default must not change what existing callers see (layoutskips Marketplace modules because it writes). A module counts as a Marketplace module by the current snapshot of the catalog only, so one that was a Marketplace module in an older snapshot is not left out of every later search. TheSEARCHstatement is unchanged. Rules: docs-site "Full-Text Search".alter navigationandalter menu— add, move, replace, drop and set menu items in place (#1522) — adding one menu item used to mean restating the whole menu.alter navigation Responsive { insert after 'Admin' > 'Users' { … }; set (Caption: 'People') on 'Admin' > 'Users'; drop 'Old'; }splices the operations into the stored document and writes it once. An item is addressed by its caption path, outer to inner, with@nfor the n-th match; a miss lists what is at that level and an ambiguity lists the shortest unique address of each candidate.check -p --referencesruns the same resolver without saving. An omitted clause keeps the stored value.- ALTER PAGE reaches every widget — a legacy table cell by position (
insert into table1 cell(1,2) { … },replace table1 cell(1,1) with { … },drop table1 cell(1,2)empties it) and the widgets inside cells; a pluggable widget's object-list items and their slots (insert into acc1 item@2 { … },acc1 item@1.headercontent) and named slots (dataGrid1.controlbar).setwrites a Gallery's or Data grid 2'sDataSource, a pluggable widget's attribute property and top-level expression properties, a container'sAction:andRenderMode, and a Boolean astrue/false. A name that misses lists the item addresses it could have meant. export leveland Protected (add-on) modules (#1498) —check -p --referencesreports what mxbuild refuses as CE1406 (a Hidden element of a Protected module referenced from another module or navigation) and CE5400 (anexport level apimicroflow exposing a Hidden entity or enumeration). Entities, attributes, associations and enumerations takeexport level api|hiddenso such an element can be made usable, a nanoflow'sexport level apiis written, anddescribe module/describe entity/show modulessay Protected and the level.- Boundary events on
call workflow(#1532) — the clausecall microflowalready takes.describeprinted it for such an activity, and the output did not parse. @anchornames a CASE outcome's sides by its WHEN value —@anchor(from: bottom, Open: (from: right), (empty): (from: bottom, to: top)) case $Order/Status …. Each outcome's side was fixed by WHEN order, and the forms that looked like they set it were refused or ignored.- Configuration JVM options reach
run --local, and are settable —alter settings configurationandcreate configurationacceptMaxJavaHeapSizeandExtraJvmParameters,describeemits them, andrun --local/test --localpass both to the runtime JVM, where they were silently absent (#131).alter settings webuisetsOptimizedClient. mxcli run --local --listen-addr— reach a local app from another machine on the network, such as a phone pointed at a dev box; it bound to 127.0.0.1 with no way to widen it.run --ensure-db --db-admin-user / --db-admin-password— a password-protected PostgreSQL superuser, as on a standard Windows install, where every passwordless path failed with "no local PostgreSQL superuser available" (#1485). The password goes to psql throughPGPASSWORD, never on the command line.- Documentation on mappings, published REST services and data transformers (#1510) — the
/** … */comment above them is stored, so QUAL002's findings can be fixed. Everycreate or modifyused to write an empty documentation over Studio Pro's text. - Lint and check — MDL-FLOW05 refuses a statement that can never run, after a
return, araise error, an if/else ending on both sides or awhile truewith no break (#1491); a snippet call carryingClass,Styleor design properties, which never reach the page, is warned (#1511); lint rules can read a user role's security flags (check_security,manage_all_roles, …). mxcli new --container-runtime docker|podman, asmxcli initalready had.
Changed
check -papplies exec's version gates (#1434) — a statement the project's Mendix version cannot store (create view entityon 9.24, …) passed check and was refused by exec after the statements before it ran. The gates come from one statement-to-feature table that check, exec's preflight and the handlers share.run --localboots with scheduled events running, and says so (#1528) — the runtime's default left every scheduled event silently off.--runtime-setting ScheduledEventExecution=…still wins.marketplace installruns the widget and design-property repairs itself (#1527), where it printed them as a next step and left CE0463 / CE6087 behind.- Faster listings on MPR v2 — unit types are indexed by content hash in
.mxcli/unit-types.idx, sodescribeof one microflow no longer opens every unit file in the project, andcreatemember assignments no longer decode every domain model.
Fixed
- A find, filter or create by an association inherited from another module keeps that association when written short (ako#1637) — over
HR.Emp extends Other.Person,describeprintsfind $l by Other.Person_Dept = $dasfind $l by Person_Dept = $d, and the builder looked a bare member up only inHR, so executing that description as a new microflow — or writing the name short by hand — stored the attributeHR.Emp.Person_Dept:check -pand exec passed, then mxbuild failed with CE1613 (11.12.5). Re-running over the stored flow said "Unchanged", so only a rebuild showed it. A bare member name now resolves through the generalization chain to an association of an ancestor in another module, after the inherited attributes. find/filterbySystem.ownerorSystem.changedBybuilds, and describe prints System associations back so they re-execute (ako#1629) —$m = find $l by System.owner = $User;was refused at exec with advice to create a System association, though mxbuild builds it (11.12.5). It now resolves to the System association; on an entity whose generalization root does not store the member,check -pand exec refuse it with the CE0091 "No member selected." mxbuild would report, naming thealter entitythat fixes it.describeprinted a stored one asby owner, and a System association inherited by a specialization ofSystem.Userasby User_Language— which re-executed as an attribute of the specialization, CE1613. A System association in a find or filter is now printed qualified (by System.User_Language), and running the description again changes nothing.marketplace diffandupdateno longer fail with "path is too long" under the default Windows%TEMP%(#1486) —mxrefuses to write any file whose path is 260 characters or longer, on every platform (measured on Linux with mx 11.12.1: a 259-character path is written, a 260-character one refused with the reported "The specified file name or path is too long"), and the reference project was built in%TEMP%\mxdiff<digits>\target-ref. The deepest files mx writes there are bundlednode_modulestrees — 181 characters in the 11.12.1 blank app, about 202 in NanoflowCommons 7.x — so that layout fitted 7.x for no user name at all. References are now built under the shortest directory the user owns (%USERPROFILE%\.mxcli\ton Windows,/tmpon Linux and macOS, orTMP/TMPDIRwhen shorter), named in one letter beneath it; never a drive-root folder such asC:\mxt, whose children every local account may modify. A package deep enough to hit the limit anyway gets an error naming the directory, its length, what it leaves for the package, and how to move it.marketplace installbuilds its reference the same way.marketplace updateandinstallno longer write the package's metadata into the app root (#1487) — after updating Nanoflow Commons 6.2.1 → 7.4.0,manifest.json,License.txtandSiemensMendixNanoflowCommons__7.4.0__READMEOSS_<timestamp>.htmlsat beside the.mpras untracked files, because every archive entry butproject.mprandpackage.xmlwas copied into the project. Only the files the package'spackage.xmllists under<files>are installed now — exactly whatmx module-importinstalls, measured on 11.12.1 with ExternalDatabaseConnector 6.3.0 and BusinessEvents 3.12.1, which includes passing over a listed file the archive lacks. What is left out (License.txt,manifest.json, the READMEOSS report,Mendix EULA.docx) is named on one line of the output.create/changenamingSystem.ownerorSystem.changedByis refused with advice that applies (#1612) —$S = create M.Entity (Name = 'x', System.owner = $User);passedcheck -p, and exec then refused it withmember "System.owner" on entity M.Entity is not a known association … create the association first (create or modify association System.owner from ... to ...), which no project can do. The member cannot be written at all: measured on 11.12.5,System.owner/System.changedByas a create or change object member is CE1050 "Unknown association 'System.owner'." for every entity — one storing its owner, one not, aSystem.FileDocumentspecialization — in a microflow and a nanoflow.check(no project needed) andexecnow refuse it as MDL-CHANGE01 and name what works: an association toSystem.Userthe project models.find $l by System.owner = $uis not affected; it builds.[id = $obj/System.owner]on an entity that stores no owner is refused before it reaches the build (ako#1306, follow-up on #1358) — the rewrite MDL-RETRIEVE02 suggests passedcheck -pand exec and then failedmx checkwith[CE0161] "Error(s) in XPath constraint."when$obj's entity does not store its owner.check -pand exec now refuse a constraint readingSystem.owner/System.changedByoff a variable whose entity does not store it, judged on the root of its generalization chain (measured on 11.12.5), and name thealter entity <root> add attribute owner: AutoOwnerthat fixes it; MDL-RETRIEVE02's suggestion states the precondition. The existing check for[System.owner = …]on the retrieved entity now also judges an entity the same script creates, which it skipped — the ordinary shape.- Rewriting a page, snippet or layout no longer deletes the widgets MDL cannot author (#1622) —
describe pageprints a classic data grid, a reference selector, a drop-down or a reference set selector as a-- NOT re-executablecomment, so executing that output rebuilt the page without them and said onlyReplaced page M.P, withmx checkclean on both sides.create or modify/create or replaceof a stored page, snippet or layout that holds such a widget — one the statement does not declare by name — is now refused, listing each widget, with thealterthat edits the document while keeping them and thealter … { drop … }that removes them on purpose first.mxcli check -preports the same before anything is written, following an earlieralterof the document in the same script. - A consumed REST service is written with Studio Pro's list markers —
Operations, an operation'sHeadersandParametersare typed-array marker 3 (onlyQueryParametersis 2), measured on Studio Pro-authored services (11.14.0) and onmx convertoutput (11.6.6). mxcli wrote 2, copied from the legacy serializer, so running a Studio Pro-made service'sdescribeoutput back rewrote it although nothing had changed — visible once that output parsed (#1608). create or modify consumed rest servicenames the operations it removes (#1608) — the statement declares the service's whole operation list, so one naming only the operations to add deleted the stored ones — often made in Studio Pro, with their headers, timeouts and mappings — and the output said onlyModified rest client: MyFirstModule.BookingApi (2 operations). It still applies, ascreate or modify entitydoes for attributes, and now prints⚠ create or modify consumed rest service MyFirstModule.BookingApi drops 1 existing operation(s) not listed in this statement: PostBookingwith thealter consumed rest service … add operationthat adds one without touching the rest.describe consumed rest serviceoutput runs again:Response: jsonparses (#1608) — an operation created withResponse: json as $Resultdescribes asResponse: json, because the variable is not stored (a consumed REST operation has none; the call activity names it), and the parser rejected that form withmissing VARIABLE at ')', so describe → edit →create or modifywas not available for any such service.Response: json | string | file | statusnow takesas $varas optional, and describe printsstring,fileandstatusresponses bare too instead of an unparseableaswith nothing after it.mxcli initandinit --sync-skillskeep a bundled lint rule you edited, and do not recreate one you renamed (#1615) — bundled rules were recognised by file name alone, so an editedexample_microflow.star(shipped to be customised) was overwritten by everymxcli initand, since v0.27.0, at every session start by the generated SessionStart hook; moving the edit to a new file name did not help, because the stock file was written back beside it and CUSTOM002 then ran twice, once from the stock list. A bundled file is now refreshed only while its content is a version mxcli shipped (cmd/mxcli/lint_rules_shipped.txt, seeded from the rules' whole git history); any other content is kept, and named once per mxcli version ("Kept 1 edited bundled lint rule(s) … delete the file to take it"). A missing bundled rule is not written while another file defines its rule ID.add-tooltakes the same path.- A specialization of a non-persistent entity reads as non-persistent (#1600) —
describe entity Probe.Childprintedcreate or modify persistent entity Probe.Child extends Probe.Base (whenProbe.Baseis non-persistent, so a reader took the child for a table and the output stated a combination Mendix does not have. A specialization stores no persistable flag of its own; it takes its root generalization's, and the reader defaulted it to persistent. The flag is now resolved along the generalization chain, across modules and into the System module (whoseHttpRequest,HttpResponseandSoapFaultread persistent for the same reason), and the fix reaches everything that reads it:describe, the catalog'sEntityType, the domain-model diagrams, andalter entities … where persistent, which used to add its attribute to such a child as well. Re-running the describe output leaves the entity unchanged. go mod tidyruns again, and the expression round-trip test runs for the first time —mdl/exprcheck/roundtrip_test.goimportedmdl/backend/mpr(the legacy engine, deleted) plusexecutor.GetHierarchyForMiningandexecutor.DescribeMicroflowToString(both removed). It never broke a build because it sat behind//go:build roundtrip, a tag no Makefile target and no CI job has ever passed — the only file in the tree using it.go mod tidyconsiders every build tag, so it was the one command that still saw the file, and it failed with "no matching versions for query latest" on the deleted package, which reads as a dependency problem rather than a dead test. The test is ported tomdl/executor/exprcheck_roundtrip_test.gounder theintegrationtag, wheresetupTestEnv/describeMDLalready provide the real-project harness andmake test-integration-executoractually runs it; the removed hierarchy helper is replaced by building the container-to-module map frombackend.ListModules(). Its assertion is worth keeping: describing every microflow and re-parsing each expression with the robust parser catches grammar gaps — a missingparsePrimarybranch, a missingfuncTableentry, a missing slot constraint — that the unit tests cannot, since those only feed the parser expressions someone thought to write down. It now also fails rather than passing silently when the fixture project yields no microflows or no expression slots.- An activity with a custom error handler is changed in place (#1489) — under
mdl 1,create or modifywith one call argument changed, or only the handler's contents changed, was refused and advisedalter; the advisedalter … replacewas refused and advisedcreate or modify. The activity replaced by one handling errors the same way is now swapped in place, keeping the stored handler, its error flow and the merge it rejoins at; the handler's body is diffed like a branch; notes on the activity come with it. A change of the handler's kind is still refused, and every remaining refusal names drop-and-create instead of the other mode. - A decision's caption change is set in place (#1610) — adding, rewording or removing an
@captionon anifwas refused as "changes where its paths end or meet". It is now written onto the stored decision, which keeps its$IDand both paths. - Notes on a decision, an end event or the canvas are changed in place (#1517) — a note added to or reworded before an
ifor the finalreturn, and any change to a free note, was refused. A decision's and an end event's notes are now replaced on the stored node, and the flow's free notes as a whole; nothing else is rebuilt. A note restated before its activity's@positionreads as a free note, and the refusal now says so and names the activity it is attached to. check -ppredicts a flow statement after an earlier change to the same flow (#1517) — check (and exec's preflight) reported such a statement "not predicted", and exec could then refuse it after the earlier statements had run. Each change check predicts as spliced is applied to an in-memory simulation — never written; any write it cannot hold is refused — so the next statement on that flow is predicted against what exec will see.describe moduleprints an entity's grants after the associations they name (#1409) — an entity's grant naming one of its associations was printed with the entity, ahead of the association, so replaying the output stopped with "entity ModA.EntA has no member(s) EntA_EntB", leaving the entity without its association or access.checkpassed the same script; it now reports a grant naming an association created further down the script, with the reorder.import from mappingover an XML schema or web service mapping imports XML (#1232) — the activity was always written with content type JSON, so re-executing a microflow whose import uses a mapping from a web service operation failedmx checkwith "The selected import mapping cannot handle content type JSON." until the activity was opened in Studio Pro. The content type now follows the mapping's source, and a stored one is kept on rewrite; a REST call's mapping result handling gets the same.- Workflow user tasks keep "Auto-assign when targeting results in one user" (#1547) — every single- and multi-user task was written with
AutoAssignSingleTargetUser = false, MDL had no spelling for it anddescribe workflowdid not print it, socreate or modify workflowturned off a box ticked in Studio Pro. When the targeting then yielded one user, the task was not assigned to them and completing it from a microflow failed at runtime with "You can't complete this user task, it is not assigned to you";checkandmx checkwere silent. The new clauseauto assign single user(onuser taskandmulti user task) writes it,describeprints it when set, and acreate or modifyorreplace activitythat does not restate a stored one is refused, likeawait all users. Omitted, it is off, which is what Studio Pro 11.14 stores for a new task. The MCP backend sentfalsetoo and now sends the task's value. checkwithout a project no longer reports a Data grid 2's own settings as dropped, or refuses an expression on one (#1573) —exec -pstoresconfigurationStorageType,configurationAttribute,storeFiltersInPersonalization,refreshInterval,columnsHidable,rowClass, a column'sminWidthand the rest of the widget's own keys, andcheck -paccepts them, butcheckalone warned MDL-WIDGET07 "not recognized and will be silently dropped on write" for each, and refused an Expression-typed one (rowClass, a column'scolumnClass) as an MDL-WIDGET42 error, so a scriptexectakes failedmake check-mdl. Data grid 2 has no embedded definition (it is generated from the project's package, and an embedded one would take precedence over that package on every write), so the check judged its keys against the built-in widgets' vocabulary. It now reads the property list of the Data grid 2 template mxcli embeds: a key the template declares is accepted and its declared type decides MDL-WIDGET42. A key it does not declare still warns — a misspelledconfigurationStorageTyptoo, with the spelling it probably meant — but says what is true without a project: that the key cannot be verified, and is dropped on write unless the installed widget declares it. With-p, and for every other widget, nothing changes.describe pageprints a Data grid 2's and a Gallery's settings that MDL has no keyword for (#1573) — a pluggable widget takes every setting by its own key (configurationStorageType: localStorage,itemSelectionMethod: rowClick,refreshInterval: 30, a column'sminWidth), andexecstores it withmx checkclean, butdescribe pageprinted only the keyword properties (PageSize,Pagination,Selection,onClick). Executing the description therefore wrote the widget's default over each of them, silently: measured on Mendix 11.12.5, a grid with 22 settings changed came back with the 4 that have keywords, and a Studio Pro-authored grid lost itsonClickTrigger: double.describenow prints each setting whose stored value differs from the default, under its key — enumerations and plain values, text templates (with their{N}parameters), expressions, attributes (bare inside a data container, qualified where a bare name would not resolve), a column's own settings and the empty-state placeholder slot. The default is read from the widget's own stored definition, not a list, and a text template counts as default when it holds the text the widget ships for it, so a grid that sets none of them describes exactly as before. A setting with no spelling (an attribute reached over associations) is named in a-- NOT re-executablecomment. A Gallery had the same loss and takes the same fix.describe pagenames the entity an association data source reaches, not the association (#1571) — under a data view, list view, grid or gallery bound over an association, the context comment read-- Context: $currentObject (Shop.Order_Customer): the association, where(Shop.Customer)belongs. The page was right (mx checkclean); the comment misled anyone readingdescribeto learn what a nested widget binds, and the same context decides how an association name inside the container is spelled. The entity is the destination stored on the source's last hop, so it is correct from either end of the association, for a reference set, and for a specialization of the association's end. TheDataSource:text is unchanged. A source with no stored destination prints what it did before.check -pandexecrefuse a navigation profile or menu document that names a page or flow that does not exist (#1569) —create or modify navigationwith a home page, role home, not-found page or menu item (at any depth, in both the deprecated and themdl 1spelling) pointing at a missing page, microflow or nanoflow passedcheck -p("Check passed!") andexec("Navigation profile 'Responsive' updated."), and onlymx checkobjected: CE1613 "The selected page 'Shop.NoSuchHome' no longer exists.", once for the home page and once per menu item. A page's ownshow pageandcall microflowand an icon name were already resolved; a navigation statement was not, and it rewrites the whole profile.check -pnow reports each unresolved target with the profile or menu document, the menu item path (menu item 'Admin' > 'Users') and the missing name, andexecrefuses the statement before it writes anything. A target the script creates resolves, wherever it stands in a run that rolls back (plainexec,check), as a call to a flow created further down does (#1405);exec --continue-on-error, the REPL and-crefuse one created further down. A standalonecreate menudocument is covered, and one stored as excluded only warns, as an excluded page does. A login page is not resolved: the sign-in page may be the theme's staticlogin.htmlrather than a page document.- A bare
returns Module.Namethat names an enumeration is accepted byexec, ascheckalready said (#1562) —create microflow M.F () returns M.Variant as $V(and the nanoflow and rule forms) passedcheck -p --referencesand was then refused byexecwith "entity 'M.Variant' not found for return type". The parser cannot tell an entity from an enumeration for a bare qualified name;execresolved that for parameters (the entity first, then an enumeration) but not for return types. All three kinds now do, and store exactly whatreturns enum M.Variantstores. A rule'scheckrefused the bare form too, with a misleading "a rule must return a Boolean or an enumeration, not Entity". A name that is both an entity and an enumeration stays the entity, as for a parameter, and a name that is neither now says "entity or enumeration" incheckand inexec, for return types and parameters alike, where it used to say only "entity".returns enum M.VariantandEnumeration(M.Variant)always worked and are whatdescribeprints. - A JavaScript action's parameters are written with the names mxbuild generates (#1560) —
create javascript action … (Url: String, ReadOnly: Boolean)wroteexport async function JS_Open(Url, ReadOnly), while mxbuild and Studio Pro regenerate the signature with the first character of each parameter lower-cased,JS_Open(url, readOnly), and keep the body. A body using the declaredUrlthen threwReferenceErrorthe first time the action ran, withcheck,mx checkand the build all clean. The signature and the JSDoc@paramnames now carry that name (only the first character changes:URLisuRL,MyIDismyID,_Leadstays), measured against mxbuild 11.12.5, so whatexecwrites is what a build leaves. The same rewrite used to break a Studio Pro-authored action on its first edit through MDL:Geocode(address, …)becameGeocode(Address, …)over a body that still readaddress. The model keeps the declared parameter names,describeprints them as declared, and bodies are not rewritten, so a body readsreadOnly, notReadOnly. - A quoted
Editable: '<expression>'is stored as a conditional editability instead of being dropped (#1567) —CHECKBOX cb (Attribute: Flag, Editable: '$currentObject/Locked = false')passedcheck,execsaid "Created page", and the widget was storedEditable: Alwayswith no condition, so a field meant to be locked stayed editable (mx checkalso reported 0 errors).Visible: '<expr>'beside it was stored as a conditional visibility, and the unquotedEditable: $currentObject/Locked = falseworked: the page builder readEditableonly as one ofAlways/Never/Conditionaland skipped every other string without a word. A string that is not one of those is now the condition, exactly as forVisible, anddescribeprints it back bare, so a describe → exec round trip is stable.Editable: false/trueon an input widget were dropped the same way and now storeNever/Always(a list view and a grid column keep their own plain boolean, and a data view keeps its existing handling).checkrefuses what still cannot be stored — a misspeltEditable: Neever, a quoted text that is not an expression,Editable: Conditionalwith no condition — as MDL-WIDGET19 naming the accepted spellings, and a quoted condition now gets the checks the bare one gets (MDL-WIDGET13 association steps, MDL-BUTTON02, MDL-WIDGET41 on a pluggable widget that declares no Editability, MDL-WIDGET20, and MDL-WIDGET31 no longer warns about an input markededitable: false).alter page … set (Editable: '<expr>')still refuses a quoted condition with its existing message. mxcli run --local --detachno longer opens a console window per child process on Windows (#1484) — a detached run on Windows made several terminal windows pop up during a boot, one for each console program it started (Java, MxBuild, Gradle). The run was created without a console (DETACHED_PROCESS), so Windows gave each console child a new, visible one. It is now created with a console of its own that has no window (CREATE_NO_WINDOW), which its children share. Measured on Mendix 11.12.5: v0.26.0 showed 4 new visible windows during a boot, this build 0. Closing the launching terminal still does not end the run, andrun stopis unchanged.mxcli test testsfinds test files in subfolders (#1363) —mxcli test tests --listprintedFound 0 test(s)when the suites live intests/<Module>/*.test.mdl, whilemxcli test tests/Sales --listfound them: a directory argument only read the files directly inside it. It now reads*.test.mdland*.test.mdat any depth, in path order, skipping hidden folders (.git,.mxcli) and folders starting with_(the Go convention, so a positive-control suite can sit intests/unit/_controloutside the default run; the folder you name is always read, somxcli test tests/unit/_controlruns it). Skipped_folders are named after theFound N test(s)line of a run and at the end of a--list, so they do not disappear silently, and--listover a tree with no tests now says why on stderr (it still exits 0; a run with no tests still exits 1). A file that cannot be parsed is still reported on its own and does not stop the others; neither does a subfolder that cannot be read (a locked folder, a root-owned one in a mounted volume), which is reported by name and turns the run red while the tests in the other folders still run. The "no tests found" hint now also names misnamed.mdl/.mdfiles in subfolders and the_folders that were skipped.changethrough an association is refused with the working idiom instead of shipping a project mxbuild rejects —change $a (A_B.Title = 'x')(a member whose dotted qualifier is an association, not a module) passedmxcli check,execreported success, and the next build failed with CE1613 "The selected association 'A_B.Title' no longer exists": Mendix's Change object activity cannot set an attribute through an association at all (probe matrix in the bug report — every encoding, including the ones an older mxcli wrote, is rejected or unloadable), and the writer's one-dot heuristic categorized the name as an association-set verbatim.check -pnow reports the form andexecrefuses it, both naming the retrieve-then-change replacement (retrieve $obj from $a/A_B;thenchange $obj (Title = …)); the legal shapes —Module.Assocassociation-sets andModule.Entity.Attributequalified attributes — are untouched. Example:mdl-examples/bug-tests/change-member-through-association.mdl.run --local --watchrecovers from twomxbuild --servefaults — on versions where mxbuild writesweb/distitself, every build after the first in a serve process dropped the pages (every page 404 after an "applied via reload"), and after a Sass error the process re-reported it on every later build even once fixed (ako#1485, #1580, #1577). The watch loop now restarts the serve process in both cases.check -ppredicts more of whatexecand the build refuse:- a page following a view entity's association from its target (CE8812), a String-returning widget expression given a non-String literal (CE0117, #1611), a combo box in enumeration mode on a non-enumeration attribute, bound to an association through
Attribute(#1440), or in association mode with no object to start from (#1420); - a list retrieved over an association used as an object (#1516), an association path as a return value or loop list, an undefined glyph code (MDL078, #1522), the variables a workflow expression names (#1529), a call workflow mapping not keyed
WorkflowContext(#1530), built-in argument kinds, also without a project (#1515), and an unknownalter settingssection or key; - a published OData entity with no KEY or a non-unique single KEY (#1426), and business event message attributes against inherited members (#1525);
- accepted where it refused: an ALTER ENTITY on an entity an earlier MOVE or RENAME produced, page data source XPath over members the same script adds, an ALTER PAGE column address no earlier operation adds, a bare System enumeration in a flow signature, and a one-hop variable comparand in a widget data source XPath (#1466).
- a page following a view entity's association from its target (CE8812), a String-returning widget expression given a non-String literal (CE0117, #1611), a combo box in enumeration mode on a non-enumeration attribute, bound to an association through
describe→execkeeps what it used to change or drop — a view entity's stored OQL (re-indented), its outer comments, and its associations (printed as statements); an XML-schema mapping's root; a gallery's sort over an association (#1599); a combo box's and a data grid filter's settings (#1586); an untitled Data grid 2 actions column (#1520); an Accordion group's header content; a call workflow's parameter mapping (#1531); business event channel identity (#1525); statements after anif, and loop bodies, inside an error handler; the line breaks an author gives an XPath constraint; a Java action parameter's description and return value name (#1588).describe module … with allcovers a large module.- Pages — a drop-down filter keeps its attribute and hidden column properties are reset after an ALTER SET (#1519); a column SET keeps the dynamic text template it hides or shows (#1617); an item keeps its hidden slots and texts; hidden text templates inside object-list items are null, as Studio Pro stores them (#1198); a control-bar
show pagecan pass the grid's selection; an older Data Widgets definition is rebuilt correctly on 10.x (#1424); a pluggable attribute of another page parameter is written$Param.Attr. - Flow layout — a splice making room leaves the flow's parameters where they are, a node the script places is not moved, a note added in a splice does not re-lay out the row, a
@curveinside a loop body is kept (#1597), and merges after loops, error-handler rows and note lines land where Studio Pro draws them. - Integration — an OpenAPI import refuses Swagger 2.0 instead of importing an empty contract; partnered OData navigation properties import (CE6618);
create external entitiesnames entities after the entity type; inherited OData members are published under their declaring entity (CE1613); a one-to-one association in a message definition builds (CE6524); a SOAP mapping's imported web service is read. - A partial
revokefrom several roles narrows each of their rules (#1501), and a note's line endings no longer count as a change to a spliced flow (#1505). - The Studio Pro MCP backend — ALTER MICROFLOW insert lands on Studio Pro 11.15, a microflow created over MCP keeps its flow sides and notes, and a foldered document's module is resolved.
- Lint builtins —
parse_xpathkeeps association paths,documents()returns message definitions,permissions()has nanoflow execute grants,scheduled_events()names the module of an event in a folder, the catalog records every document a widget names (#1513), anduser_roles()exposes a role's description. - Tools —
mxcli newscaffolds a layout that builds on Mendix 10.24 and fails when it does not (#1433);create configurationon 11.12+ mints fresh ids for the copied OpenTelemetry part;describe settingsshows the project id (#1526);git note --writeno longer loses notes when its reader exits early;docker checkno longer claims widgets were normalised afterupdate-widgetsfailed; the devcontainer'spostCreateCommandworks on Podman on Windows; syntax errors hint a keyword used as a name,returnsafter option clauses (#1533) and a routine header without parentheses (#1514).
Security
- The deprecated v1 BSON driver is gone: mxcli runs on
mongo-driver/v2alone —go.mongodb.org/mongo-driverv1.17.10 is removed fromgo.mod, andgo version -m bin/mxclinow lists onlymongo-driver/v2 v2.9.2. It was the last component left on a customer's blocking dependency scan, flagged for CVE-2026-107325, which has no fix on the v1 line and never will — v1.17.10 is that line's final release and the module is marked deprecated, so no upgrade could ever have cleared it. mxcli was never exposed (the panic is inbsoncore.Array.Validate, which no file imported), but the finding would have recurred on every scan. - New
modelsdk/bsoncompat— the decode shims that make v2 read like v1 — the two drivers decode nested documents into different Go types, and mxcli's type assertions were written against v1's. Measured on identical bytes: abson.Ddestination nestsprimitive.Dunder v1 andbson.Dunder v2 (equivalent); abson.Mdestination nestsprimitive.Munder v1 butbson.Dunder v2; amap[string]anydestination nestsmap[string]anyunder v1 butbson.Dunder v2. The last two silently break adoc["Key"].(bson.M)read — a failed type assertion is not an error, so the value is dropped and the next write emits a document with a property missing, which Studio Pro refuses to merge (thecanon.CompletePropertySetsclass, #1373).UnmarshalDocMandUnmarshalDocMaprestore v1's shape at every depth, arrays included;DMapreplaces thebson.D.Map()method v2 removed. The two Unmarshal helpers are not interchangeable —bson.Mis a named type, so abson.Mvalue does not satisfy.(map[string]any)— and abson.Ddestination needs neither, since applying one there re-introduces the bug inverted. All three shapes are pinned by tests that fail loudly if a future driver release makes a helper unnecessary, rather than passing vacuously. - A guard keeps v1 from coming back —
TestNoV1DriverImportswalks the tree for the v1 import path andTestGoModDoesNotRequireV1Driverreadsgo.mod, because re-adding either is not a build error and nothing else would notice until the next dependency scan. Verified by control: both name the offending file and the offendinggo.modline. - The
klauspost/compressv1.20.3 floor survivesgo mod tidy— the v1 BSON driver removal rango mod tidy, which deleted the hand-writtengithub.com/klauspost/compress v1.20.3requirement, because no package mxcli builds imports it. The module graph fell back to the v1.19.2 thatmongo-driver/v2declares, which is flagged for GHSA-fpmh-frq8-7rj8, so the dependency scan would have blocked the release again. A newinternal/depfloor/floor.goimportsgithub.com/klauspost/compress/zstdbehind adepfloorbuild tag that no build sets.go mod tidyconsiders every tag, so the requirement is now direct and kept, whilego version -m bin/mxclistill lists noklauspost/compress.TestGoModKeepsSecurityFloorsfails when go.mod lacks a floored module or requires it below its floor, and it also pinsmongo-driver/v2at v2.9.2. - Dependency scan follow-up:
klauspost/compressandgo-mssqldbraised to their fixed versions —github.com/klauspost/compressv1.19.2 -> v1.20.3 andgithub.com/microsoft/go-mssqldbv1.10.0 -> v1.11.2. A second scan of the published.exe, run after the mongo-driver upgrade, still blocked the release.klauspost/compressv1.19.2 predates GHSA-fpmh-frq8-7rj8 (a zstdDecodeAllcrash on crafted input, fixed in v1.20.2) — the earlier upgrade had only raised it to whatevermongo-driver/v2declared, which is not a security floor; it is now an explicit requirement. As before, that module is compiled into no mxcli build (nogo.sumentry, nogo version -mline), so the bump moves only the requirement graph the scanner reads.go-mssqldbis a blank import insql/drivers.gofor driver registration and mxcli calls none of its API, so the bump has no API surface;TestExternalDriversRegisterednow pins that contract, which had no coverage, and fails withdriver "sqlserver" is not registeredwhen the import is dropped. go.mongodb.org/mongo-driverv1 remains flagged and cannot be upgraded out — CVE-2026-107325 has no fix on the v1 line (v1.17.10 is its last release and the module is deprecated). mxcli is not exposed: the panic is inbsoncore.Array.Validate, which mxcli imports in no file, every.Validate()call site in the tree resolves to the v2 driver, and v1'sbson.Unmarshal— the API mxcli does use, on.mprbytes — does not panic on the malformed payloads tried. Measured on v1.17.10:bsoncore.Array.Validate([]byte{0,0,0,0})panics withindex out of range [-1], whilebson.Raw.Validateon CVE-2026-107324'smath.MaxInt32payload does not, so that second CVE's code path is v2-only and the v1 report overstates it by one. Clearing the finding at source needs the remaining v1bsonimports moved to v2, which is tracked separately.- Go 1.27.3 for GO-2026-6809, reported by govulncheck.
- npm advisories cleared in the vega-charts widget and the VS Code extension — in-range audit fixes, and
sprintf-jsdropped from the vega-charts widget.
New Contributors
- @kaspergff made their first contribution in #744
Full Changelog: v0.27.0...v0.28.0