Order MDL statements so that every reference resolves — execution is sequential and immediate, so a document must exist before anything points at it. Use when a script fails on a reference to something defined later in the same file.
Scanned 10/4/2026
npx -y skills add mendixlabs/mxcli --skill resolve-forward-references --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Resolve Forward References?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mendixlabs-resolve-forward-references)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: resolve-forward-references
description: "Order MDL statements so that every reference resolves — execution is sequential and immediate, so a document must exist before anything points at it. Use when a script fails on a reference to something defined later in the same file."
---
# Resolving Forward References in MDL Scripts
## Why Forward References Fail
MDL script execution is **sequential and immediate** — each `CREATE` statement commits
its document to the project database before the next statement runs. When a document is
being built, all its references (snippets, pages, microflows) are resolved against the
database at that moment. A reference to something defined *later in the same script* fails
because it is not in the database yet.
```
Error: snippet not found: MyModule.NavMenu
```
This applies to the following reference types:
| Reference | In | Fails when |
|---|---|---|
| `snippetcall` | page / snippet | snippet created after the page |
| `show page` in action | page / snippet | page created after the page that references it |
| `SHOW PAGE` | microflow | page created after the microflow |
| `call microflow` | microflow | callee microflow created after the caller (in the same script, `exec` resolves the call against the project/backend, not later same-script definitions — so order the callee first) |
> **Note:** `SHOW PAGE` inside a microflow body resolves the page reference at
> microflow-creation time, not at invocation time. If the target page doesn't exist yet,
> the microflow creation fails.
---
## The Placeholder Pattern
The standard workaround is a three-step sequence:
1. **Create a minimal placeholder** for the document that will be referenced.
2. **Create all documents that reference it.** They bind to the placeholder's ID.
3. **Fill in the placeholder** using `CREATE OR MODIFY` or `ALTER` — both preserve the
original ID so existing bindings remain valid.
> **Critical:** Never use `CREATE OR REPLACE` for the fill-in step. `OR REPLACE` deletes
> the placeholder and creates a new document with a different ID. Every page or snippet
> that references the placeholder immediately becomes a dangling reference.
---
## Pattern 1 — Shared Navigation Snippet (most common)
A navigation snippet contains `show page` buttons (references pages) and pages include
the snippet via `snippetcall` (references the snippet). Both sides reference each other.
```sql
mdl 1;
-- Step 1: placeholder snippet (minimal valid content)
create snippet MyModule.NavMenu
{
layoutgrid g { row { column (desktopwidth: 12) {
dynamictext loading (content: 'Loading...')
}}}
};
-- Step 2: pages that embed the snippet (snippet already exists → resolves OK)
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row {
column (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.NavMenu)
}
column (desktopwidth: 9) {
datagrid dg (datasource: database MyModule.Customer) { }
}
}}
};
create page MyModule.Order_Overview
(
title: 'Orders',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row {
column (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.NavMenu)
}
column (desktopwidth: 9) {
datagrid dg (datasource: database MyModule.Order) { }
}
}}
};
-- Step 3: fill in the snippet with real content (pages now exist → show page resolves OK)
-- Use CREATE OR MODIFY (preserves ID) or ALTER SNIPPET (in-place)
create or modify snippet MyModule.NavMenu
{
layoutgrid g { row { column (desktopwidth: 12) {
actionbutton btnCustomers (
caption: 'Customers',
action: show page MyModule.Customer_Overview
)
actionbutton btnOrders (
caption: 'Orders',
action: show page MyModule.Order_Overview
)
}}}
};
```
---
## Pattern 2 — Page References Another Page (new/edit from overview)
An overview page has a New button that opens a NewEdit page via `show page`. The NewEdit
page must exist before the overview can reference it.
```sql
mdl 1;
-- Solution: declare the target page first (even if empty), then the referencing page
create page MyModule.Customer_NewEdit
(
params: ( $Customer: MyModule.Customer ),
title: 'Edit Customer',
layout: Atlas_Core.PopupLayout
)
{
layoutgrid g { row { column (desktopwidth: 12) {
dataview dv (datasource: $Customer) {
textbox txtName (label: 'Name', attribute: Name)
}
actionbutton btnSave (caption: 'Save', action: save changes)
actionbutton btnCancel (caption: 'Cancel', action: cancel changes)
}}}
};
-- Now the overview can safely reference the NewEdit page
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row { column (desktopwidth: 12) {
actionbutton btnNew (
caption: 'New',
action: call microflow MyModule.ACT_Customer_New
)
datagrid dg (datasource: database MyModule.Customer) {
column (caption: 'Name', attribute: Name)
}
}}}
};
```
For simple cases, reordering declarations is sufficient and no placeholder is needed.
---
## Pattern 3 — Microflow References a Page Not Yet Created
```sql
mdl 1;
-- If the page is defined later in the script, create a placeholder or reorder.
-- Easiest fix: declare the page before the microflow that shows it.
-- Page first
create page MyModule.Order_Detail
(
params: ( $Order: MyModule.Order ),
title: 'Order Detail',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row { column (desktopwidth: 12) {
dataview dv (datasource: $Order) {
textbox txtID (label: 'Order ID', attribute: OrderID)
}
}}}
};
-- Microflow after the page it references
create microflow MyModule.ACT_OpenOrder ($Order: MyModule.Order)
begin
@position(200,200)
show page MyModule.Order_Detail (Order = $Order);
@position(400,200) return;
end;
```
---
## Ordering Rules for Dependency-Free Scripts
To avoid forward references entirely, follow this declaration order within a script:
```
1. Entities and associations (no cross-document references)
2. Enumerations and constants (no cross-document references)
3. Snippets (placeholder if needed)
4. Pages (reference snippets + other pages)
5. Snippets (fill-in step, if placeholder was used)
6. Microflows and nanoflows (reference pages, entities)
7. Navigation (references pages)
```
When generating MDL scripts, write sections in this order. Doing so avoids the placeholder
pattern for the majority of scripts.
---
## Choosing Between CREATE OR MODIFY and ALTER SNIPPET
Both preserve the snippet's ID. Use whichever fits:
| Approach | When to use |
|---|---|
| `create or modify snippet` | Rewriting the whole snippet body from scratch |
| `alter snippet` | Inserting or replacing specific widgets within an existing layout |
```sql
mdl 1;
-- ALTER SNIPPET: targeted widget replacement (keeps surrounding structure)
alter snippet MyModule.NavMenu {
replace loading with {
actionbutton btnCustomers (
caption: 'Customers',
action: show page MyModule.Customer_Overview
)
}
};
```
---
## Script Template for a Full CRUD Module
```sql
mdl 1;
-- ============================================================
-- MyModule CRUD scaffold
-- Correct declaration order: snippets → pages → microflows → nav
-- ============================================================
-- 1. Placeholder for shared navigation (will reference pages created below)
create snippet MyModule.AppNav
{
layoutgrid g { row { column (desktopwidth: 12) {
dynamictext placeholder (content: '...')
}}}
};
-- 2. NewEdit page (referenced by Overview's New button)
create page MyModule.Customer_NewEdit
(
params: ( $Customer: MyModule.Customer ),
title: 'Edit Customer',
layout: Atlas_Core.PopupLayout
)
{
-- ... widgets ...
};
-- 3. Overview page (references NewEdit + NavMenu)
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row {
column (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.AppNav)
}
column (desktopwidth: 9) {
-- ... datagrid with New button calling ACT_Customer_New ...
}
}}
};
-- 4. Fill in navigation (pages now exist)
create or modify snippet MyModule.AppNav
{
layoutgrid g { row { column (desktopwidth: 12) {
actionbutton btnCustomers (
caption: 'Customers',
action: show page MyModule.Customer_Overview
)
}}}
};
-- 5. Microflows (pages already exist)
create microflow MyModule.ACT_Customer_New ()
begin
$c = create MyModule.Customer ();
show page MyModule.Customer_NewEdit (Customer = $c);
return;
end;
-- 6. Navigation (pages already exist). This statement sets the whole profile,
-- its menu included: list every item the menu must keep.
create or modify navigation Responsive
home page MyModule.Customer_Overview
{
menu item 'Customers' ( OnClick: show page MyModule.Customer_Overview )
};
```
---
## Related Skills
- [Create Page](../create-page/SKILL.md) — Full page syntax reference
- [Overview Pages](../overview-pages/SKILL.md) — Overview + NewEdit page patterns
- [ALTER PAGE/SNIPPET](../alter-page/SKILL.md) — In-place snippet modification
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!