Agent skill

Alter Page

by mendixlabs in mendixlabs/mxcli

Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout.

Apache-2.0Auto-check passed

Install Alter Page

skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install mendixlabs/mxcli alter-page --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/mendix/alter-page .claude/skills/alter-page && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
alter-page
GitHub stars
128
Token cost
~7k tokens
SKILL.md length
3,010 words
Files
1
Skills in repo
72
Repo updated
First seen
Licence
Apache-2.0

At a glance

Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout.

  • Works in 5 steps: Get widget names first: Run describe… → Check syntax: mxcli check script.mdl → Check references: mxcli check script.mdl… → …
  • Changing a caption
  • SKILL.md covers Overview, When to Use, Syntax and Operations, plus 7 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Alter Page is an agent skill from mendixlabs/mxcli. Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout. Use when changing a caption, style or property, adding or removing a widget, or reordering a form, instead of rewriting the whole page with CREATE OR REPLACE. The only safe way to change a page or snippet authored in Studio Pro.

Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

The repository describes itself as: Mendix cli tool, a headless way to work with Mendix projects. Enables Mendix projects for use with 3rd party agentic coding tools like Claude Code and Copilot. Includes a… The licence is Apache-2.0.

When your agent uses it

  • Changing a caption
  • Removing a widget
  • Reordering a form
  • Instead of rewriting the whole page with CREATE OR REPLACE

Example prompts

  • “/alter-page”

Workflow steps

5 steps, taken from the first numbered list in SKILL.md.

  1. Get widget names first: Run describe page Module.PageName to see all widget names
  2. Check syntax: mxcli check script.mdl
  3. Check references: mxcli check script.mdl -p app.mpr --references
  4. Verify result: Run describe page Module.PageName after ALTER to confirm changes
  5. Validate project: mxcli docker check -p app.mpr (or mxcli docker check -p app.mpr)

What it can do on your machine

Read from SKILL.md and the folder at commit 20a6c89. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are sql and mdl).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Alter Page loads about 7k tokens when it runs. Until then it costs about 94 tokens; SKILL.md has 3,010 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~94
When it runs · the whole SKILL.md, loaded when a task matches
~7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from mendixlabs/mxcli at commit 20a6c89, republished under its Apache-2.0 licence (© mendixlabs). 3,010 words, ~7,013 tokens.

Download SKILL.mdSave it as .claude/skills/alter-page/SKILL.md (or your agent's skills folder).
name
alter-page
description
Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout. Use when changing a caption, style or property, adding or removing a widget, or reordering a form, instead of rewriting the whole page with CREATE OR REPLACE. The only safe way to change a page or snippet authored in Studio Pro.

ALTER PAGE / ALTER SNIPPET - Modify Existing Pages and Snippets

Overview

ALTER PAGE and ALTER SNIPPET modify an existing page or snippet's widget tree in-place without requiring a full create or modify. Operations work directly on the raw BSON tree, preserving widget types and properties that MDL doesn't explicitly model.

When to Use

ScenarioUse
Change a button caption, label, or stylealter page with set
Add a field to an existing formalter page with insert
Remove unused widgetsalter page with drop
Replace a footer or sectionalter page with replace
Several related changes on the same pagealter page with multiple operations in one block
Same property across many pages (e.g., add Class to every Container)update widgets — see bulk-widget-updates
Rebuild entire page from scratch (pages your scripts own only)create or modify page
Create a new pagecreate page

Rule of thumb:

  • alter page — targeted edits to one page. Combine multiple ops in one block when they belong together.
  • update widgets — cross-page bulk updates with WHERE filtering and DRY RUN.
  • create or modify page — redefining the full page structure of a page your MDL scripts own.

Who owns the page decides (choose-edit-mode). A page or snippet authored or edited in Studio Pro is changed with alter, however large the change. describe → edit → create or modify is only for pages your MDL scripts created and nobody has changed in Studio Pro since: on a Studio Pro page that round trip has dropped translations and filled an empty English caption from another language, and on a snippet it has dropped the snippet's type.

Syntax

sql
alter page Module.PageName {
  operation1;
  operation2;
  ...
};

alter snippet Module.SnippetName {
  operation1;
  operation2;
  ...
};

This is the generic ALTER — the same four verbs for every document type (alter layout too):

sql
alter page Module.PageName {
  set (Key: value, …) on <target>;      -- no `on`: the page itself
  insert before|after|into <target> { <widgets as in create page> }
  replace <target> with { <widgets> }
  drop <target>, <target>;
};

A <target> on a page is a widget name, a DataGrid 2 column grid column(Attr) (or grid column('Caption')), or a layout region layoutContainer.top. Properties go in parentheses with :, exactly as in create page. The older spellings set Key = value on w, set Key: value (no parentheses) and drop widget w still run, and warn with MDL-DEPR101, MDL-DEPR102 and MDL-DEPR103 — write the form above.

Multiple operations can be combined in a single ALTER statement. They are applied sequentially; later operations see the page state produced by earlier ones, so you can set on a widget you just inserted.

sql
mdl 1;
-- Rename a column, add a sibling, drop an obsolete one — all in one block.
alter page MyMod.Product_Overview {
  set (caption: 'Product Name') on dgProducts column(Name);
  insert after dgProducts column(Lifecycle) {
    column (attribute: Sku, caption: 'SKU')
  };
  drop dgProducts column('Old')
};

For changes that should be applied across many pages (e.g., "add Class='card' to every Container in MyMod"), use UPDATE WIDGETS instead — see bulk-widget-updates.

Operations

List View Specialization Templates

A List View template has no name, so it cannot be reached by a widget ref like every other target. Adding one reuses INSERT INTO with the same template for block create page uses — a template has one spelling everywhere. Removing one has its own form:

sql
mdl 1;
alter page Pages.Vehicle_Overview {
  insert into vehicleListView {
    template for Pages.Motorcycle {
      dynamictext mcLabel (content: 'Motorcycle {1}', contentparams: ({1} = Brand))
    }
  };
  drop template for Pages.SUV in vehicleListView
};

Naming the list view in the drop is required, not optional: one page can hold two list views with a template for the same entity.

Most template edits need none of this. The widgets inside a template are ordinary named widgets, so set (content: '…') on busLabel and insert after busLabel { … } already work and land in the right template. To replace a whole template, drop it and insert the new one in the same block — operations apply in order.

Refused, each naming the problem:

  • insert before / insert after a template — templates are not siblings of the widgets in the list view's body, so only insert into makes sense.
  • mixing template for … blocks with ordinary widgets in one insert — they go to different places (the Templates array and the default body). Use two inserts.
  • a template for an entity that is not the list view's entity or a specialization of it — it could never match an object the list view shows.
  • a second template for an entity that already has one.
  • drop template for an entity with no template — the error names the ones that are there, because dropping nothing and reporting success is how a typo becomes a silent no-op.
SET - Modify Widget Properties
sql
-- Single property
set (caption: 'New Caption') on widgetName

-- Multiple properties
set (caption: 'Save & Close', buttonstyle: success) on btnSave

-- Page-level property (no ON clause). Page-level property names are
-- case-sensitive and must match the Mendix property exactly.
set (Title: 'New Page Title')

-- Pop-up dimensions (apply when the page is opened in a pop-up)
set (PopupWidth: 800)
set (PopupHeight: 480)
set (PopupResizable: true)
set (Documentation: 'What this page is for.')

-- Retarget a button's on-click action. Any form `create page` accepts works
-- here, including the combined ones.
set (Action: call microflow Module.ACT_Other) on btnSave
set (Action: SAVE CHANGES CLOSE PAGE) on btnSave
set (Action: SHOW PAGE Module.DetailPage) on btnEdit

-- Retarget ONE named action slot of a pluggable widget, by the widget's own
-- property key (the same key `create page` takes: `createFileAction: …`).
set ('createFileAction': call microflow Module.ACT_CreateFile) on fileUploader1
set ('onSelectionChange': show page Module.Detail) on dgOrders

-- Rebind a data-bound widget
set (DataSource: $OrderParam) on dvOrder
set (DataSource: microflow Module.MF_Get) on dvOrder

Prefer set Action over replace when only the action changes. replace rebuilds the widget from what the statement says, so any property you do not restate — ButtonStyle, Class, design properties, tooltip — is dropped. set edits the one property and leaves the rest of the widget alone.

The exception is a pluggable widget replaced by one of the same kind (a combo box by a combo box): there replace keeps every stored property the statement does not change — a translated placeholder, readOnlyStyle, anything MDL has no word for — and writes only what differs from the widget as describe prints it. So a sort can be added to a combo box's options by restating its describe line with sort by appended.

set Action is refused on a widget that has no action (a plain container, say), rather than writing a property the widget type does not define — Studio Pro refuses to open a document with an unknown property while MxBuild tolerates it, so a silent write would build cleanly and then fail to open.

Supported SET properties:

PropertyWidget TypesValue TypeExample
ActionWidgets with an on-click action (ACTIONBUTTON, LINKBUTTON, clickable containers)Any create page action expressionset (Action: call microflow M.ACT_Go) on btnSave
'<slotKey>'Pluggable widgets — any action-typed property (File Uploader createFileAction, DataGrid 2 onSelectionChange, …)Any create page action expressionset ('createFileAction': call microflow M.ACT_Create) on fileUploader1 — refused, naming the widget's action slots, if the key is not action-typed
captionACTIONBUTTON, LINKBUTTONStringset (caption: 'Submit') on btnSave
contentDYNAMICTEXTStringset (content: 'New Heading') on txtTitle
labelTEXTBOX, TEXTAREA, DATEPICKER, COMBOBOX, CHECKBOX, RADIOBUTTONSStringset (label: 'full Name') on txtName
buttonstyleACTIONBUTTON, LINKBUTTONPrimary, Default, Success, Danger, Warning, Infoset (buttonstyle: danger) on btnDelete
classAny widgetCSS class stringset (class: 'card mx-2') on container1
styleAny widget (see warning below)Inline CSS stringset (style: 'padding: 16px;') on container1
editableInput widgetsStringset (editable: 'Never') on txtReadOnly
visibleAny widgetString or Booleanset (visible: false) on txtHidden
NameAny widgetStringset (Name: 'newName') on oldName
TitlePage-level only (case-sensitive)Stringset (Title: 'Edit Customer')
DocumentationPage-level only (case-sensitive)String ('' clears)set (Documentation: 'Coordinator triage step.')
layoutPage-level onlyQualified nameset layout = Atlas_Core.Atlas_Default
PopupWidthPage-level only (case-sensitive)Positive integer (pixels)set (PopupWidth: 800)
PopupHeightPage-level only (case-sensitive)Positive integer (pixels)set (PopupHeight: 480)
PopupResizablePage-level only (case-sensitive)Booleanset (PopupResizable: true)
ClassPage-level (case-sensitive, no ON)CSS class stringset (Class: 'container-fluid bg-light')
StylePage-level (case-sensitive, no ON)Inline CSS stringset (Style: 'min-height: 100vh')
Visible (conditional)Any widgetexpressionset (Visible: $currentObject/Name != '') on ctnDetails
Editable (conditional)Input widgetsexpressionset (Editable: $currentObject/Active) on txtName
'quotedProp'Pluggable widgetsString, Boolean, Numberset ('showLabel': false) on cbStatus

Conditional visibility/editability — set (Visible: <expr>) on widget (and Editable) attach a per-object client expression, stored as written: name attributes as $currentObject/Name. The bracketed set (Visible: [Name != '']), which roots a bare attribute for you, still works and warns MDL-DEPR081. Setting Editable on a non-input widget is rejected. This mirrors CREATE PAGE's visible: — see the create-page skill for enum-value rules.

Pluggable widget properties use quoted names to set values in the widget's Object.Properties[]. Boolean values are stored as "yes"/"no" in BSON.

Column property names are case-insensitive in MDL — set (caption: …) and set (Caption: …) both work. The internal BSON keys are dictated by the widget schema and stay case-sensitive on the storage side.

Warning: Style on DYNAMICTEXT — Setting style directly on a DYNAMICTEXT widget crashes MxBuild with a NullReferenceException. Wrap the DYNAMICTEXT in a CONTAINER and apply styling to the container instead:

sql
-- Wrong: crashes MxBuild
SET (Style: 'color: red;') ON txtHeading

-- Correct: style the container
REPLACE txtHeading WITH {
  CONTAINER ctnHeading (Style: 'color: red;') {
    DYNAMICTEXT txtHeading (Content: 'Heading', RenderMode: H2)
  }
}
Changing a widget's DataSource

SET DataSource retypes a data source in place — including across shapes, e.g. from a microflow to a page parameter:

sql
mdl 1;
ALTER PAGE MyModule.OrderPage {
  SET (DataSource: $Order) ON dvOrder;                       -- page/snippet parameter
  SET (DataSource: microflow MyModule.MF_Get) ON dvOrder;     -- microflow
  SET (DataSource: nanoflow MyModule.NF_Get) ON dvOrder;      -- nanoflow
  SET (DataSource: selection dgOrders) ON dvDetail;           -- listen to widget
};

The parameter must exist on the page (or snippet) being altered — its entity is read from the container's own parameter list, and an unknown name is refused rather than written as an unresolved reference.

association and database sources are not supported by SET. Use REPLACE for those, which rebuilds the widget through the CREATE PAGE path and handles every datasource type; the error message says so.

A database source has no single stored shape — the widget holding it decides which element Mendix writes (a list view, a data grid and a pluggable widget each store a different one), and SET writes the property directly rather than rebuilding the widget, so it has nothing to choose from. This used to be accepted and half-applied: the widget was left with a source that DESCRIBE read back as absent and mxbuild rejected as CE7007, on a page exec had just reported as altered (mendixlabs/mxcli#1032).

A data view is the one case REPLACE does not rescue: it binds to a single object, so Mendix gives it no database form at all and the CREATE PAGE path refuses one too. Point it at a context parameter, a microflow, a nanoflow or selection <widget>, and use a list view or a data grid to show a query. The refusal says which of the two situations you are in.

INSERT - Add Widgets
sql
-- Insert after a widget
insert after txtName {
  textbox txtMiddleName (label: 'Middle Name', attribute: MiddleName)
}

-- Insert before a widget
insert before btnSave {
  actionbutton btnPreview (caption: 'Preview', action: call microflow Module.ACT_Preview)
}

-- Insert INTO a container — append as its last child (works on an EMPTY container)
insert into ctnToolbar {
  actionbutton btnNew (caption: 'New', action: nothing, buttonstyle: primary)
}

Inserted widgets use the same syntax as create page. Multiple widgets can be inserted in a single block.

insert into <container> appends as the last child of the named container — the only way to fill an empty container, and handy for adding to a container/dataview without needing a sibling to anchor to. Widgets inserted into a dataview take that dataview's entity as their context. Supported on simple containers (container, dataview, groupbox, tab page, scroll-container region); for a layout grid, insert relative to a widget inside the target column instead.

Adding a tab: insert into <tabcontainer> { tabpage … } appends a tab page, and insert after|before <tabpage> { tabpage … } places it next to that sibling. A tab page cannot go next to an ordinary widget or inside another tab page, and one insert cannot mix tab pages and widgets. Dropping or reordering tab pages still needs create or modify page.

The context comes from the nearest enclosing data source, whatever kind it is — a database or association source, a microflow/nanoflow source (the entity is the flow's return type), or datasource: selection <list>, which takes the entity of the list it listens to. A bare attribute in the inserted or replaced widget resolves against that entity, exactly as it would in create page. When no enclosing source can be resolved, the binding is written unset rather than guessed at — describe page then prints <unbound>, and mxbuild reports CE0402 "No value specified.", so re-describe the page after an ALTER that moves data-bound widgets.

DROP - Remove Widgets
sql
-- Drop a single widget
drop txtUnused

-- Drop multiple widgets
drop txtOldField, lblOldLabel, container2

Removes widgets and their entire subtree from the page.

REPLACE - Replace Widget Subtree
sql
mdl 1;
-- Replace a data view's footer (it has no name: address it by its data view)
alter page MyModule.Customer_Edit {
  replace dvMain.footer with {
    footer {
      actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary)
      actionbutton btnCancel (caption: 'Cancel', action: cancel changes)
    }
  }
};

Replaces the target widget with one or more new widgets. The new widgets use the same syntax as create page, and may reuse the names of the widgets the replace removes. insert into dvMain.footer { … } appends to a footer and drop dvMain.footer empties it.

Show full SKILL.md (1,182 more words)Show less
DataGrid Column Operations

A DataGrid 2 column is addressed by what describe page prints in it: grid column(Attr) for its attribute:, grid column('Caption') for its caption:. See "DataGrid 2 columns: how to address them" below.

sql
-- SET a column property
set (caption: 'Product SKU') on dgProducts column(Code)

-- DROP a column
drop dgProducts column('Old column')

-- INSERT a column after an existing one
insert after dgProducts column(Price) {
  column (attribute: Margin, caption: 'Margin')
}

-- REPLACE a column
replace dgProducts column(Description) with {
  column (attribute: Notes, caption: 'Notes')
}

-- Two columns over one attribute: pick one with @n
drop dgProducts column(Name)@2

The older dotted form gridName.columnName still works; it matches a name mxcli derives (the short attribute name, else the sanitized caption, else colN).

ADD Variables - Add a Page Variable
sql
add variables $showStockColumn: boolean = 'true'

Adds a new page variable (Forms$LocalVariable) to the page/snippet. DataType can be boolean, string, integer, decimal, datetime, or an entity type. Default value is a Mendix expression in single quotes.

DROP Variables - Remove a Page Variable
sql
drop variables $showStockColumn

Removes a page variable by name.

SET Layout - Change Page Layout
sql
-- Auto-map placeholders by name (most common case)
set layout = Atlas_Core.Atlas_Default

-- Explicit mapping when placeholder names differ
set layout = Atlas_Core.Atlas_SideBar map (Main as content, Extra as Sidebar)

Changes the page's layout without rebuilding the widget tree. Only rewrites the FormCall.Form and FormCall.Arguments[].Parameter BSON fields — all widget content is preserved. Not supported for snippets.

When placeholders have the same names in both layouts (e.g., both have Main), auto-mapping works. Use map when placeholder names differ between the old and new layout.

Examples

Change button text and style
sql
mdl 1;
alter page MyModule.Customer_Edit {
  set (caption: 'Save & Close', buttonstyle: success) on btnSave
};
Add a field to a form
sql
mdl 1;
alter page MyModule.Customer_Edit {
  insert after txtEmail {
    textbox txtPhone (label: 'Phone', attribute: Phone)
  }
};
Add a page variable for column visibility
sql
mdl 1;
alter page MyModule.ProductOverview {
  add variables $showStockColumn: boolean = 'if (3 < 4) then true else false'
};
Remove unused fields and update title
sql
mdl 1;
alter page MyModule.Customer_Edit {
  set (title: 'Edit Customer Details');
  drop txtLegacyField, lblOldNote;
  set (label: 'Email Address') on txtEmail
};
sql
mdl 1;
alter page MyModule.Customer_Edit {
  replace dvMain.footer with {
    footer {
      actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success)
      actionbutton btnDelete (caption: 'Delete', action: delete, buttonstyle: danger)
      actionbutton btnCancel (caption: 'Cancel', action: cancel changes)
    }
  }
};
Modify a snippet
sql
mdl 1;
alter snippet MyModule.NavigationMenu {
  set (caption: 'Dashboard') on btnHome;
  insert after btnHome {
    actionbutton btnReports (caption: 'Reports', action: show page MyModule.Reports_Overview)
  }
};
Set pluggable widget properties
sql
mdl 1;
alter page MyModule.Customer_Edit {
  set ('showLabel': false) on cbStatus;
  set ('labelWidth': 4) on cbCategory
};

DataGrid 2 columns: how to address them, and what you can set

Mendix stores no column name. A DataGrid 2 column's schema has no name or identifier key — the only human-facing label is its caption — so a column is written without one, and describe page prints none:

mdl
create or modify page Mod.P (...) {
  datagrid dg1 (datasource: database Mod.Item) {
    column (attribute: Label, caption: 'The Label')
    column (caption: 'Actions', ShowContentAs: customContent) { … }
  }
};

A name written there anyway (column colLabel (…), the old describe output) is dropped and reported as MDL-DEPR005; mxcli fmt --upgrade removes it.

ALTER PAGE addresses a column by what describe prints in it — its attribute: value, or its caption::

mdl
mdl 1;
alter page Mod.P { set (Caption: 'Renamed') on dg1 column(Label) };       -- the column bound to Label
alter page Mod.P { drop dg1 column('Actions') };                           -- the column captioned Actions
alter page Mod.P { set (Sortable: false) on dg1 column(Owner/Name) };      -- over an association, as describe writes it

Two columns over the same attribute (or with the same caption) share the address. ALTER refuses it rather than picking one, and the error lists the matches — add @n to choose: drop dg1 column(FullName)@2.

The older dg1.Label form still works: it addresses a column by a name mxcli derives (the attribute's short name, else the sanitized caption, else colN by position). Prefer column(…), which says what it matches.

Setting column properties

Property names resolve against the keys the installed widget declares, so both the schema key and mxcli's MDL alias work (DynamicCellClass and ColumnClass both reach columnClass). An unknown name lists what is settable on that grid.

DynamicCellClass (and a widget's DynamicClasses) take a Mendix expression, written as-is. A quoted value is a Mendix string, so a literal CSS class is just the quoted class name, and a computed one is the expression itself:

mdl
mdl 1;
-- a literal class: the string 'highlight'
alter page Mod.P { SET (DynamicCellClass: 'highlight') ON dg1 column(Label) };

-- a computed class
alter page Mod.P { SET (DynamicCellClass: if $currentObject/Price > 100 then 'highlight' else '') ON dg1 column(Label) };
text
-- WRONG: a bare name is an identifier, not a string — mxbuild reports CE0117
alter page Mod.P { SET (DynamicCellClass: highlight) ON dg1 column(Label) };

The old spelling — the expression's text in quotes, 'if … then ''a'' else ''''' — is refused under mdl 1; as MDL-WIDGET33, because there it stores that text as a class name. A script without the header keeps its old meaning and warns MDL-V1-QUOTEDEXPR; mxcli fmt --upgrade writes it bare. This applies equally to create page; the two paths behave identically.

A column's pluggable Visible expression is not converted yet: there a quoted value is still the expression's text, so a literal needs the doubled quotes.

Properties holding a structured value — attribute, filter, content, actions — cannot be set by ALTER at all. It refuses them and points at create or modify page, rather than writing a string where Mendix expects a reference.

Widget property names are matched case-insensitively, pluggable ones included, so a spelling CREATE PAGE accepts is a spelling ALTER PAGE accepts — set (PageSize: 10) on dgProducts and set (pageSize: 10) on dgProducts are the same statement. This is what makes DESCRIBE output re-executable: describe page prints the capitalised PageSize:, while the widget template stores pageSize (mendixlabs/mxcli#1069). A property the widget does not declare is still an error — and mxcli check … --references reports it before the script runs, so a typo no longer lands halfway through. The pre-flight resolves the name against the stored document rather than a list, so it is right about whatever widget package this project has installed; the error names the widget's own property keys. ON a widget the page does not have is caught the same way.

Two things it deliberately stays quiet about, because it cannot answer them: a page the script itself creates (nothing is stored yet — the widgets there are checked where they are written), and a widget an INSERT in the same script adds. Both still fail at exec if they are genuinely wrong.

Common Mistakes

MistakeFix
Missing on widgetName for widget SETAdd on widgetName (only page-level properties — Title, Documentation, PopupWidth, PopupHeight, PopupResizable, Class, Style — omit ON)
unsupported page-level property: titlePage-level property names are case-sensitive — use Title, PopupWidth, PopupHeight, PopupResizable, Class, Style
Using unquoted pluggable property namesQuote pluggable props: set ('showLabel': false) on cb
pluggable property "X" not foundThe widget does not declare it — casing is not the problem (any casing resolves). The error lists the keys it does declare; describe widget type <type> or describe page shows them in context. Run mxcli check … --references to get this before the script runs
Wrong widget nameUse describe page Module.Name to see widget names
SET on non-existent widgetWidget names are case-sensitive; check with DESCRIBE
Missing semicolons between operationsEach operation inside { } ends with ;

Limitations — prefer binding at page creation (ledger finding #45)

ALTER PAGE is best for content edits (add/remove/retitle widgets). When you hit the limit below, define the referenced microflows before the page and bind the buttons at creation time instead of rewiring afterwards:

  1. SET cannot rewire a button's action. set accepts a fixed property list (caption, class, visible, …) — action is not on it, so set Action = call microflow … on btnSave is a parse error. Set the button's action when the button is created (or REPLACE the button subtree).

A data view's footer has no name. Its widgets are stored in the data view, and the footer itself is not, so a name written on it is never kept (MDL-DEPR005) and describe prints footer { … }. Address it by its data view: replace dvMain.footer with { footer { … } }, insert into dvMain.footer { … }, drop dvMain.footer.

Recommended pattern: put save/reset microflows in a file that runs before the page definition, and bind the popup/footer buttons to them at creation. The apparent "page needs microflow, microflow needs page" cycle usually exists only between different microflows, not within the page itself.

Validation Checklist

  1. Get widget names first: Run describe page Module.PageName to see all widget names
  2. Check syntax: mxcli check script.mdl
  3. Check references: mxcli check script.mdl -p app.mpr --references
  4. Verify result: Run describe page Module.PageName after ALTER to confirm changes
  5. Validate project: mxcli docker check -p app.mpr (or mxcli docker check -p app.mpr)
  • describe page Module.PageName - View current page structure (get widget names)
  • describe snippet Module.SnippetName - View current snippet structure
  • create [or replace] page - Create or fully rebuild a page
  • create [or replace] snippet - Create or fully rebuild a snippet
  • update widgets set ... where ... - Bulk update widget properties across pages
  • drop page Module.PageName - Delete a page
  • drop snippet Module.SnippetName - Delete a snippet

© mendixlabs, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/mendix/alter-page of mendixlabs/mxcli.

Open the folder on GitHubat commit 20a6c89

Compare with similar skills

Alter Page next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Alter Page compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Alter Page this skillmendixlabs/mxcli128—~7kAutomated safety check: PassApache-2.0
Makepad Widgetssickn33/agentic-awesome-skills47k2 repos~1.7kAutomated safety check: PassMIT
Contact Widgetnexu-io/open-design100k—~1.6kAutomated safety check: PassApache-2.0
More Trees AutomationComposioHQ/awesome-claude-skills77k3 repos~742Automated safety check: PassNone
WidgetLeoYeAI/openclaw-master-skills2.2k—~1.9kAutomated safety check: PassMIT
Chat Widgetsickn33/agentic-awesome-skills47k2 repos~332Automated safety check: PassMIT

Similar skills

  • Makepad Widgets

    sickn33/agentic-awesome-skills

    Version: makepad-widgets (dev branch) | Last Updated: 2026-01-19 Check for updates: https://crates.io/crates/makepad-widgets

    47k GitHub starsUsed in 2 repos~1.7k tokens
    Auto-check passed
  • Contact Widget

    nexu-io/open-design

    Self-contained floating chat widget with welcome screen, social links, meeting button, and message input.

    100k GitHub stars~1.6k tokensUpdated today
    Auto-check passed
  • More Trees Automation

    ComposioHQ/awesome-claude-skills

    Automate More Trees tasks via Rube MCP (Composio). An agent skill from ComposioHQ/awesome-claude-skills.

    77k GitHub starsUsed in 3 repos~742 tokens
    Productivity & AutomationAuto-check passed
  • Widget

    LeoYeAI/openclaw-master-skills

    Create, update, hide, show, list, and delete Übersicht desktop widgets on macOS.

    2.2k GitHub stars~1.9k tokensUpdated 2 mo ago
    Auto-check passed
  • Chat Widget

    sickn33/agentic-awesome-skills

    Build a real-time support chat system with a floating widget for users and an admin dashboard for support staff.

    47k GitHub starsUsed in 2 repos~332 tokens
    Sales & SupportAuto-check passed
  • Pierre Trees File Tree

    pierrecomputer/pierre

    Use when an app uses @pierre/trees to render or control a file tree, including React, vanilla JavaScript, SSR, web components, selection, search, rename, drag…

    6.2k GitHub stars~473 tokensUpdated today
    Frontend & DesignAuto-check passed

More from mendixlabs/mxcli

All 72 skills in this repo
  • Mendix Odata Pushdown

    mendixlabs/mxcli

    Push OData query options into the SQL of a Mendix resource served by a read microflow, so $filter, $orderby, $top, $skip, $count and the key lookup reach the database instead of being silently…

    128 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Mendix Vega Charts

    mendixlabs/mxcli

    Chart a Mendix app with Vega-Lite through a pluggable widget that takes the specification and the data as separate properties, so the model emits rows and never assembles a chart payload.

    128 GitHub stars~3k tokensUpdated today
    Auto-check passed
  • Agents

    mendixlabs/mxcli

    Author Mendix AI agent documents in MDL — Model, Knowledge Base, Consumed MCP Service and Agent, with variables, tools and multi-line prompts.

    128 GitHub starsUsed in 1 repo~2.2k tokens
    Auto-check passed
  • Mendix Bulk Oql Dml

    mendixlabs/mxcli

    Run set-based INSERT, UPDATE and DELETE against Mendix entities through OQL statements, which the runtime supports and Studio Pro cannot author.

    128 GitHub stars~1.4k tokensUpdated today
    Auto-check passed
  • Business Events

    mendixlabs/mxcli

    Define event-driven APIs over Kafka with Mendix business event services — publish and subscribe contracts, CREATE/DROP/DESCRIBE.

    128 GitHub starsUsed in 1 repo~1.3k tokens
    Auto-check passed
  • Catalog Search

    mendixlabs/mxcli

    Search the Mendix Catalog platform service registry (catalog.mendix.com) from the CLI to find services published across an organisation.

    128 GitHub starsUsed in 1 repo~1.4k tokens
    Auto-check passed

Questions about Alter Page

What does Alter Page do?

Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout. Alter Page is an agent skill from mendixlabs/mxcli. Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout.

When should I use Alter Page?

Alter Page fits situations like: changing a caption; removing a widget; reordering a form; instead of rewriting the whole page with CREATE OR REPLACE.

How do I install Alter Page in Claude Code?

Run `npx skills add mendixlabs/mxcli --skill alter-page -a claude-code`. Or copy the skill folder (.claude/skills/mendix/alter-page in mendixlabs/mxcli) into .claude/skills/alter-page in your project. Claude Code loads it when a task matches its description.

How do I install Alter Page in Codex?

Run `npx skills add mendixlabs/mxcli --skill alter-page -a codex`. Or copy the skill folder (.claude/skills/mendix/alter-page in mendixlabs/mxcli) into .agents/skills/alter-page in your project. Codex loads it when a task matches its description.

Can I use Alter Page in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mendixlabs/mxcli --skill alter-page -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/alter-page, .gemini/skills/alter-page, .github/skills/alter-page and .opencode/skills/alter-page in your project.

What does Alter Page need to run?

SKILL.md names no scripts, command-line tools or credentials: Alter Page is instructions for the agent only.

Does Alter Page access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Alter Page safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Alter Page use?

Alter Page is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Alter Page use?

About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Alter Page?

Skills that share tags, products or a category with Alter Page: Makepad Widgets (sickn33/agentic-awesome-skills, 47k stars), Contact Widget (nexu-io/open-design, 100k stars), More Trees Automation (ComposioHQ/awesome-claude-skills, 77k stars) and Widget (LeoYeAI/openclaw-master-skills, 2.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Alter Page?

mendixlabs (a GitHub organization) maintains it in mendixlabs/mxcli, which has 128 GitHub stars. The repository holds 72 skills in this directory. The repository was last updated on October 7, 2026.

Source: mendixlabs/mxcli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.