Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .claude/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
Type this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a codex
Project install goes to .agents/skills/; add -g for ~/.codex/skills/.
Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .agents/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a cursor
Project install goes to .agents/skills/; add -g for ~/.cursor/skills/.
Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .cursor/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a gemini-cli
Project install goes to .agents/skills/; add -g for ~/.gemini/skills/.
Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .gemini/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
GitHub CLI
$ gh skill install mendixlabs/mxcli alter-page
Installs for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a github-copilot
Project install goes to .agents/skills/; add -g for ~/.copilot/skills/.
Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .github/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add mendixlabs/mxcli --skill alter-page -a opencode
OpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
Install the "alter-page" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/alter-page into .opencode/skills/alter-page/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "alter-page", then confirm the skill loads.
OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
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.
1Get widget names first: Run describe page Module.PageName to see all widget names
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.
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
Scenario
Use
Change a button caption, label, or style
alter page with set
Add a field to an existing form
alter page with insert
Remove unused widgets
alter page with drop
Replace a footer or section
alter page with replace
Several related changes on the same page
alter 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 page
create 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:
Property
Widget Types
Value Type
Example
Action
Widgets with an on-click action (ACTIONBUTTON, LINKBUTTON, clickable containers)
set (Visible: $currentObject/Name != '') on ctnDetails
Editable (conditional)
Input widgets
expression
set (Editable: $currentObject/Active) on txtName
'quotedProp'
Pluggable widgets
String, Boolean, Number
set ('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
};
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:
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.
Page-level property names are case-sensitive — use Title, PopupWidth, PopupHeight, PopupResizable, Class, Style
Using unquoted pluggable property names
Quote pluggable props: set ('showLabel': false) on cb
pluggable property "X" not found
The 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 name
Use describe page Module.Name to see widget names
SET on non-existent widget
Widget names are case-sensitive; check with DESCRIBE
Missing semicolons between operations
Each 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:
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
Get widget names first: Run describe page Module.PageName to see all widget names
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.
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…
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…
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.
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.