Skip to content

ALTER PAGE … SET ImageUrl = … ON <image> reports "Altered page" and changes nothing: the pluggable setter writes PrimitiveValue into a TextTemplate-kind property, so DESCRIBE, mx check and the runtime never see it #1201

Description

@MendixMau

Environment: mxcli v0.24.0 (2026-09-24T05:44:35Z). Same result on v0.23.0 (2026-09-21T11:10:32Z) and on a local build of upstream main at 86d395ee. mdl/backend/pagemutator/mutator.go has no change between v0.24.0 and 86d395ee. Mendix 11.12.2 (mx check from mxbuild 11.12.2). Fresh app from mxcli new App --version 11.12.2, macOS. The widget is the built-in Image (com.mendix.widget.web.image.Image) that ships with a fresh app.

Summary: ALTER PAGE … { SET ImageUrl = <value> ON <imageWidget> } passes mxcli check --references, and exec prints Altered page …. But the page does not change: DESCRIBE still shows the old URL and mx check stays at 0 errors. The value goes into the property's PrimitiveValue, while imageUrl is a TextTemplate-kind property, and Studio Pro, DESCRIBE and the runtime read its TextTemplate. This happens with every value form we tried: attribute path, text template, plain literal, and both key spellings. A SET of a primitive property on the same widget (Width) works. REPLACE with the create form works.

Steps to reproduce

  1. mxcli new App --version 11.12.2.
  2. Domain, then a page with one image in a data view and one in a list view:
create module "Catalog";
create persistent entity "Catalog"."Item" ("Name": String(100), "LogoUrl": String(400));
create module role "Catalog"."User";
grant "Catalog"."User" on "Catalog"."Item" (create, delete, read *, write *);

create page "Catalog"."Item_View" (
  Title: 'Item', Layout: Atlas_Core.Atlas_Default, Params: { $Item: "Catalog"."Item" }
) {
  dataview "dvItem" (DataSource: $Item) {
    image "imgDv" (ImageType: imageUrl, ImageUrl: 'placeholder', WidthUnit: pixels, Width: 58)
  }
  listview "lvItems" (DataSource: database "Catalog"."Item") {
    image "imgLv" (ImageType: imageUrl, ImageUrl: 'placeholder', WidthUnit: pixels, Width: 58)
  }
}
  1. Run each of these as its own script, and after each one run describe page Catalog.Item_View and mx check:
alter page "Catalog"."Item_View" { set ImageUrl = ["LogoUrl"] on "imgDv" };
alter page "Catalog"."Item_View" { set ImageUrl = [LogoUrl] on imgLv };
alter page "Catalog"."Item_View" { set ImageUrl = '{LogoUrl}' on "imgDv" };
alter page "Catalog"."Item_View" { set ImageUrl = '{LogoUrl}' on "imgLv" };
alter page "Catalog"."Item_View" { set ImageUrl = 'https://example.com/logo.png' on "imgDv" };
alter page "Catalog"."Item_View" { set imageUrl = 'https://example.com/lower.png' on "imgLv" };
  1. Control on the same widget: alter page "Catalog"."Item_View" { set Width = 64 on "imgDv" };

Actual (v0.24.0; v0.23.0 and main give the same output line for line)

$ mxcli check b03-set-bracket.mdl -p App.mpr --references
Check passed!
$ mxcli exec b03-set-bracket.mdl -p App.mpr
Connected to: App.mpr (Mendix 11.12.2)
Altered page Catalog.Item_View
Altered page Catalog.Item_View
$ mxcli -p App.mpr -c 'describe page Catalog.Item_View' | grep -n 'image '
8:    image imgDv (ImageType: imageUrl, ImageUrl: 'placeholder', WidthUnit: pixels, Width: 58)
12:    image imgLv (ImageType: imageUrl, ImageUrl: 'placeholder', WidthUnit: pixels, Width: 58)
$ mx check App.mpr
The app contains: 0 errors.

The DESCRIBE lines stay the same after every one of the six SETs, and mx check stays at 0 errors after each one: 6 of 6 SETs report success and change nothing that is read. The control changes what it should:

8:    image imgDv (ImageType: imageUrl, ImageUrl: 'placeholder', WidthUnit: pixels, Width: 64)

Where the values went, read from mxcli bson dump --type page --object Catalog.Item_View --format json (the widget's imageUrl WidgetValue) after each step:

after create:  imgDv.imageUrl  PrimitiveValue=''                              TextTemplate.Text=['placeholder']  Parameters=0
["LogoUrl"]:   imgDv.imageUrl  PrimitiveValue='["LogoUrl"]'                   TextTemplate.Text=['placeholder']  Parameters=0
[LogoUrl]:     imgLv.imageUrl  PrimitiveValue='[LogoUrl]'                     TextTemplate.Text=['placeholder']  Parameters=0
'{LogoUrl}':   imgDv.imageUrl  PrimitiveValue='{LogoUrl}'                     TextTemplate.Text=['placeholder']  Parameters=0
literal:       imgDv.imageUrl  PrimitiveValue='https://example.com/logo.png'  TextTemplate.Text=['placeholder']  Parameters=0
imageUrl key:  imgLv.imageUrl  PrimitiveValue='https://example.com/lower.png' TextTemplate.Text=['placeholder']  Parameters=0

Expected: one of two outcomes. Either the SET lands in the field the property's kind uses: for imageUrl (TextTemplate) that is TextTemplate.Text, and '{LogoUrl}' should bind a parameter the way the create form does. Or the SET is refused with an error. A success message over a value nothing reads is the one outcome that should not happen.

For reference, the create form, applied through REPLACE, stores the TextTemplate correctly:

alter page "Catalog"."Item_View" {
  replace "imgDv" with {
    image "imgDv" (ImageType: imageUrl, ImageUrl: '{LogoUrl}', WidthUnit: pixels, Width: 58)
  }
};
8:    image imgDv (ImageType: imageUrl, ImageUrl: '{1}', ImageUrlParams: [{1} = LogoUrl], WidthUnit: pixels, Width: 58)
imgDv.imageUrl  PrimitiveValue=''  TextTemplate.Text=['{1}']  Parameters=1
The app contains: 0 errors.

Cause (from reading main at 86d395ee):

  • mdl/executor/cmd_alter_page.go:223: a plain SET value goes to mutator.SetWidgetProperty(op.Target.Widget, propName, value).
  • mdl/backend/pagemutator/mutator.go:2710-2712: setRawWidgetPropertyMut has no case for ImageUrl, so its default branch calls setPluggableWidgetPropertyMut.
  • mutator.go:2935-2979: setPluggableWidgetPropertyMut finds the property by key, case-insensitively (strings.EqualFold, :2955). It then writes bsonnav.DSet(valDoc, "PrimitiveValue", …) for every Go type of value (:2960-2973) and returns nil. It never looks at the value kind the widget schema declares.
  • mdl/executor/cmd_pages_describe_pluggable.go:1183 reads imageUrl from the TextTemplate, which is why DESCRIBE shows no change.
  • The sibling for DataGrid 2 columns had exactly this bug and was fixed in c07a7ff0 ("schema-shaped column writes", for ALTER PAGE SET DynamicCellClass ON <column> passes check --references but fails at exec with "column property not found" #919). There, columnValueField (mutator.go:2329) picks Expression, TextTemplate or PrimitiveValue from the schema kind and refuses structured kinds. Its comment describes the same symptom: "SET DynamicCellClass and SET Visible both reported success, wrote a value Studio Pro does not read, did not survive a DESCRIBE round trip, and left mx check at 0 errors." setPluggableWidgetPropertyMut did not get that treatment.

Not tested: Expression-kind properties of pluggable widgets. They go through the same setPluggableWidgetPropertyMut path, so they are likely affected the same way, but we only verified the TextTemplate case (imageUrl). We did not boot the app, so the runtime effect is inferred from the stored model, not observed.

Suggested fixes, ranked

  1. Give setPluggableWidgetPropertyMut the same schema-kind switch the column setter uses (reuse columnValueField). For TextTemplate kinds, build the value with the same template-and-parameters builder the create path uses, so that SET ImageUrl = '{LogoUrl}' gives '{1}' + [{1} = LogoUrl], exactly like CREATE/REPLACE. Refuse structured kinds with an error. A test: create → SET → DESCRIBE must show the new value, for one TextTemplate, one Expression and one primitive property.
  2. At minimum, return an error when the target property's kind is not primitive. That turns a silent no-op into a visible failure, and the REPLACE workaround already exists.

Workaround: REPLACE the widget with its full create form (shown under Expected). It stores the TextTemplate with its parameter, and mx check stays at 0 errors.

Retest: steps 1–3, then describe page Catalog.Item_View. Pass = after set ImageUrl = '{LogoUrl}' on "imgDv" the line reads ImageUrl: '{1}', ImageUrlParams: [{1} = LogoUrl] (the same as the REPLACE output above), and after the literal SET it reads ImageUrl: 'https://example.com/logo.png'. Also acceptable: exec fails with an error naming the property. Not acceptable: Altered page with DESCRIBE unchanged.

Related: #919 (closed, the same defect class on DataGrid 2 columns, fixed by c07a7ff), #928 (closed, imageUrl TextTemplate parameters dropped on the create path, not ALTER), #292 (open, SET Title/SET Label silently not persisting on built-in widgets, a different setter), #157 (open, general ALTER PAGE limits, mentions ImageUrl only as a property CREATE OR REPLACE drops).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions