write-nanoflows · git:20260910.6710094 · 2026-09-10 · sha256 b2d2048ac0fd4a30
write-nanoflows git:20260910.6710094A
Immutable. This exact content is served forever at /api/v1/blob/b2d2048ac0fd4a30.
---
name: write-nanoflows
description: "Nanoflow syntax in MDL — shared with microflows, but client-side and restricted. Use before writing any CREATE NANOFLOW, when a nanoflow validation error needs explaining, or when deciding between a nanoflow and a microflow."
---
# Mendix Nanoflow Skill
This skill provides guidance for writing Mendix nanoflows in MDL syntax. Nanoflows share syntax with microflows but execute client-side with restricted capabilities.
## When to Use This Skill
Use this skill when:
- Writing CREATE NANOFLOW statements
- Debugging nanoflow validation errors
- Understanding nanoflow restrictions vs microflows
- Building mobile or offline-capable features
If you're not sure whether the logic belongs in a nanoflow or a microflow, read the next section first. The mirror lives in [write-microflows](../write-microflows/SKILL.md) — keep both copies in sync.
## When to Use a Nanoflow vs a Microflow
| Scenario | Use |
|----------|-----|
| Client-side form validation before save | Nanoflow |
| UI navigation and page routing | Nanoflow |
| Calling device features (GPS, phone, camera) | Nanoflow |
| Offline data access and local storage | Nanoflow |
| Calling JavaScript actions (NanoflowCommons) | Nanoflow |
| Showing progress indicators / confirmation dialogs | Nanoflow |
| Querying the database | Microflow |
| Calling REST services or external actions | Microflow |
| Running Java actions | Microflow |
| File generation or download | Microflow |
| Transactional commits (rollback on error) | Microflow |
| Background scheduled logic | Microflow |
**Rule of thumb:** A nanoflow runs before the server call. A microflow IS the server call.
## Key Differences from Microflows
| Aspect | Microflow | Nanoflow |
|--------|-----------|----------|
| **Execution** | Server-side | Client-side (browser/mobile) |
| **Database access** | Full | No direct access |
| **Transactions** | Supported | Not supported |
| **Java actions** | Supported | Not supported |
| **JavaScript actions** | Not supported | Supported |
| **SYNCHRONIZE** | Not available | Available (offline sync) |
| **File downloads** | Supported | Not supported |
| **Error handling** | Full `ON ERROR` blocks + `RAISE ERROR` | Per-action `ON ERROR` supported; `RAISE ERROR` / `ErrorEvent` forbidden |
| **Offline** | Not available | Available |
| **Binary return type** | Supported | Not supported |
## Nanoflow Structure
```mdl
/**
* Nanoflow description
*
* @param $Parameter1 Description
* @returns Description of return value
*/
CREATE [OR MODIFY] NANOFLOW Module.NAV_Name (
$Parameter1: type
)
RETURNS ReturnType
FOLDER 'FolderPath'
BEGIN
-- Nanoflow logic here
RETURN $Result;
END;
```
**Every body statement ends with a semicolon `;`** — required, not optional, exactly as
in microflows. That includes block terminators: `end if;`, `end loop;`, `end while;`,
`end case;`. A missing one is a parse error (`missing ';' at 'return'`), not a warning.
## Naming Convention
Nanoflow names use the `NAV_` prefix by convention:
- `NAV_ValidateCart` — client-side validation
- `NAV_ShowDetails` — page navigation
- `NAV_ToggleFilter` — UI state toggle
- `NAV_SignIn` — authentication
- `NAV_SyncChanges` — offline sync
## Supported Activities
### Object Operations (in-memory only)
```mdl
$Item = CREATE Sales.CartItem (Quantity = 1);
CHANGE $Item (Quantity = $Item/Quantity + 1);
COMMIT $Item;
DELETE $Item;
ROLLBACK $Item;
```
### Calling Other Flows
```mdl
$Result = CALL NANOFLOW Sales.NAV_ValidateCart (Cart = $Cart);
$ServerResult = CALL MICROFLOW Sales.ACT_SubmitOrder (Order = $Order);
$JsResult = CALL JAVASCRIPT ACTION NanoflowCommons.SignIn (userName = $Name, password = $Pass);
```
### UI Activities
```mdl
SHOW PAGE Sales.CartDetail ($Cart = $Cart);
CLOSE PAGE;
SHOW MESSAGE WARNING 'Connection unavailable. Working offline.';
VALIDATION FEEDBACK $Item/Quantity MESSAGE 'Quantity must be at least 1';
```
### Logging and Variables
```mdl
LOG INFO 'Cart updated with ' + toString($ItemCount) + ' items';
DECLARE $IsValid Boolean = true;
SET $IsValid = false;
```
**Where nanoflow log output goes (and a filtering trap):** a nanoflow runs on the
client, so its `LOG` output takes two paths:
- **Browser console** — with automatic timing, e.g.
`[Nanoflow] [flow_…] Starting execution of Sudoku.NF_ToggleNotes` … `Finished … 6.7 ms`.
- **Server runtime log** (`.mxcli/runtime.log` under `run --local`) — but the runtime
**rewrites the log node** to `Client_Nanoflow`. So `LOG INFO NODE 'Sudoku' '…'` from a
nanoflow appears as `Client_Nanoflow: …`, **not** `Sudoku: …`. A log filter built
around your microflow node names will silently drop every nanoflow line — grep for
`Client_Nanoflow` (or the message text) to see nanoflow logs. This is a Mendix
platform behaviour, not an mxcli one.
- **`LOG DEBUG` never reaches the server log.** Only `INFO`/`WARNING`/`ERROR` reach
`runtime.log`; a nanoflow `LOG DEBUG` line is sent but dropped server-side. All four
levels still show in the **browser console**, so use the console (not `runtime.log`)
when debugging at `DEBUG` level.
### Control Flow
```mdl
IF $Cart/ItemCount = 0 THEN
VALIDATION FEEDBACK $Cart/ItemCount MESSAGE 'Cart is empty';
RETURN false;
ELSE
SHOW PAGE Sales.Checkout ($Cart = $Cart);
RETURN true;
END IF;
```
### Offline Sync
```mdl
-- Sync uncommitted changes back to server
SYNCHRONIZE $Item;
```
`SYNCHRONIZE` is nanoflow-only. Use it after committing offline objects to push changes to the server in a native mobile context.
---
## Real-World Patterns
These patterns come from 223 nanoflows across three production Mendix apps:
EnquiriesManagement (79), Evora-FactoryManagement (93), LatoProductInventory (51).
### Pattern 1: Client-Side Validation
Validate before calling a microflow to avoid a round-trip.
```mdl
/**
* Validates the enquiry form fields before submission.
* @param $Enquiry The enquiry being created or edited
* @returns true if valid, false if validation errors were shown
*/
CREATE OR MODIFY NANOFLOW Enquiries.NAV_ValidateEnquiry (
$Enquiry: Enquiries.Enquiry
)
RETURNS Boolean
FOLDER 'Validation'
BEGIN
IF $Enquiry/Subject = '' THEN
VALIDATION FEEDBACK $Enquiry/Subject MESSAGE 'Subject is required';
RETURN false;
END IF;
IF $Enquiry/ContactEmail = '' THEN
VALIDATION FEEDBACK $Enquiry/ContactEmail MESSAGE 'Email is required';
RETURN false;
END IF;
RETURN true;
END;
```
### Pattern 2: Navigation Controller
Validate then navigate — keeps pages dumb.
```mdl
/**
* Validates the product and opens the detail page if valid.
* @param $Product Product to open
*/
CREATE OR MODIFY NANOFLOW Inventory.NAV_OpenProductDetail (
$Product: Inventory.Product
)
FOLDER 'Navigation'
BEGIN
$IsValid = CALL NANOFLOW Inventory.NAV_ValidateProduct ($Product = $Product);
IF NOT ($IsValid) THEN
RETURN;
END IF;
SHOW PAGE Inventory.ProductDetail ($Product = $Product);
END;
```
### Pattern 3: UI Feedback Wrapper
Wrap a slow server call with progress indicators.
```mdl
/**
* Shows progress, calls the server, hides progress.
* @param $Order The order to submit
*/
CREATE OR MODIFY NANOFLOW Sales.NAV_SubmitOrderWithProgress (
$Order: Sales.Order
)
FOLDER 'Actions'
BEGIN
CALL JAVASCRIPT ACTION NanoflowCommons.ShowProgress (message = 'Submitting order...');
$Result = CALL MICROFLOW Sales.ACT_SubmitOrder (Order = $Order);
CALL JAVASCRIPT ACTION NanoflowCommons.HideProgress ();
IF $Result THEN
SHOW MESSAGE SUCCESS 'Order submitted successfully.';
END IF;
END;
```
### Pattern 4: Confirmation Dialog Before Destructive Action
```mdl
/**
* Asks the user to confirm before deleting an item.
* @param $Item The inventory item to delete
*/
CREATE OR MODIFY NANOFLOW Inventory.NAV_ConfirmDeleteItem (
$Item: Inventory.Item
)
FOLDER 'Actions'
BEGIN
$Confirmed = CALL JAVASCRIPT ACTION NanoflowCommons.ShowConfirmation (
question = 'Delete ' + $Item/Name + '?',
positiveButtonCaption = 'Delete',
cancelButtonCaption = 'Cancel'
);
IF NOT ($Confirmed) THEN
RETURN;
END IF;
CALL MICROFLOW Inventory.ACT_DeleteItem (Item = $Item);
CLOSE PAGE;
END;
```
### Pattern 5: Authentication Flow
```mdl
/**
* Signs the user in and navigates to the home page on success.
* @param $Username Login username
* @param $Password Login password
*/
CREATE OR MODIFY NANOFLOW Auth.NAV_SignIn (
$Username: String,
$Password: String
)
FOLDER 'Authentication'
BEGIN
$StatusCode = CALL JAVASCRIPT ACTION NanoflowCommons.SignIn (
userName = $Username,
password = $Password,
useAuthToken = true
);
IF $StatusCode = 200 THEN
SHOW PAGE Home.HomePage ();
ELSE IF $StatusCode = 401 THEN
SHOW MESSAGE ERROR 'Incorrect username or password.';
ELSE
SHOW MESSAGE WARNING 'Could not connect to server (status: ' + toString($StatusCode) + ')';
END IF;
END;
```
### Pattern 6: Connectivity Check Before Server Call
Common in field-service and factory-management apps where offline is normal.
```mdl
/**
* Submits inspection results, warns user if offline.
* @param $Inspection The inspection to submit
*/
CREATE OR MODIFY NANOFLOW Factory.NAV_SubmitInspection (
$Inspection: Factory.Inspection
)
FOLDER 'Inspections'
BEGIN
$IsOnline = CALL JAVASCRIPT ACTION NanoflowCommons.IsConnectedToServer ();
IF NOT ($IsOnline) THEN
SHOW MESSAGE WARNING 'You are offline. Changes will sync when reconnected.';
COMMIT $Inspection;
RETURN;
END IF;
COMMIT $Inspection;
SYNCHRONIZE $Inspection;
CALL MICROFLOW Factory.ACT_ProcessInspection (Inspection = $Inspection);
END;
```
### Pattern 7: Geolocation Capture
Used in enquiry and field-service apps to tag records with GPS coordinates.
```mdl
/**
* Captures the current GPS position and stores it on the record.
* @param $Record The record to tag with location
*/
CREATE OR MODIFY NANOFLOW Enquiries.NAV_CaptureLocation (
$Record: Enquiries.SiteVisit
)
FOLDER 'Location'
BEGIN
$Location = CALL JAVASCRIPT ACTION NanoflowCommons.GetCurrentLocation (
timeout = 10000,
maximumAge = 0,
highAccuracy = true
);
CHANGE $Record (
Latitude = $Location/Latitude,
Longitude = $Location/Longitude,
LocationTimestamp = $Location/Timestamp
);
END;
```
### Pattern 8: Platform-Conditional Logic
```mdl
/**
* Opens a map or shows coordinates depending on platform.
* @param $Latitude Latitude coordinate
* @param $Longitude Longitude coordinate
*/
CREATE OR MODIFY NANOFLOW Enquiries.NAV_ShowOnMap (
$Latitude: Decimal,
$Longitude: Decimal
)
FOLDER 'Location'
BEGIN
$Platform = CALL JAVASCRIPT ACTION NanoflowCommons.GetPlatform ();
IF $Platform = 'Native_mobile' THEN
CALL JAVASCRIPT ACTION NanoflowCommons.OpenMap (
latitude = $Latitude,
longitude = $Longitude
);
ELSE
SHOW PAGE Enquiries.LocationDetail (Lat = $Latitude, Lon = $Longitude);
END IF;
END;
```
### Pattern 9: Local Storage Cache
Store a frequently accessed value locally to avoid a round-trip.
```mdl
/**
* Loads the last-used filter value from local storage.
* @returns The cached filter string, or empty if never set
*/
CREATE OR MODIFY NANOFLOW Inventory.NAV_LoadFilterCache ()
RETURNS String
FOLDER 'Filters'
BEGIN
$Exists = CALL JAVASCRIPT ACTION NanoflowCommons.StorageItemExists (
key = 'InventoryFilter'
);
IF NOT ($Exists) THEN
RETURN '';
END IF;
$FilterValue = CALL JAVASCRIPT ACTION NanoflowCommons.GetStorageItemString (
key = 'InventoryFilter'
);
RETURN $FilterValue;
END;
```
### Pattern 10: Device Communication (Field Apps)
```mdl
/**
* Lets the user call the on-site contact directly.
* @param $Contact The site contact to call
*/
CREATE OR MODIFY NANOFLOW Factory.NAV_CallContact (
$Contact: Factory.Contact
)
FOLDER 'Communication'
BEGIN
IF $Contact/Phone = '' THEN
SHOW MESSAGE WARNING 'No phone number on record for ' + $Contact/Name;
RETURN;
END IF;
CALL JAVASCRIPT ACTION NanoflowCommons.CallPhoneNumber (
phoneNumber = $Contact/Phone
);
END;
```
---
## NanoflowCommons JavaScript Actions Reference
All three test apps include the `NanoflowCommons` module. The actions below are available in any app that has this module installed.
### Authentication & Connectivity
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.SignIn` | `userName`, `password`, `useAuthToken` | Integer | 200=success, 401=bad creds, 0=offline |
| `NanoflowCommons.SignOut` | — | — | Logs out current user |
| `NanoflowCommons.IsConnectedToServer` | — | Boolean | false when offline |
### Device Features
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.GetCurrentLocation` | `timeout`, `maximumAge`, `highAccuracy` | `NanoflowCommons.Geolocation` | Object has Lat, Lon, Accuracy, Timestamp |
| `NanoflowCommons.Geocode` | `address`, `provider`, `apiKey` | `NanoflowCommons.Geolocation` | Address → coordinates |
| `NanoflowCommons.ReverseGeocode` | `latitude`, `longitude`, `provider`, `apiKey` | String | Coordinates → address |
| `NanoflowCommons.GetStraightLineDistance` | `fromLat`, `fromLon`, `toLat`, `toLon`, `distanceUnit` | Decimal | Haversine distance |
| `NanoflowCommons.OpenMap` | `latitude`, `longitude` | — | Opens maps app |
| `NanoflowCommons.CallPhoneNumber` | `phoneNumber` | — | Opens dialer |
| `NanoflowCommons.SendTextMessage` | `phoneNumber` | — | Opens SMS app |
| `NanoflowCommons.DraftEmail` | `to`, `subject`, `body` | — | Opens email client |
| `NanoflowCommons.Share` | `content` | — | Native share dialog |
| `NanoflowCommons.OpenURL` | `url` | — | Opens in browser |
| `NanoflowCommons.GetPlatform` | — | String | `Web`, `Native_mobile`, `Hybrid_mobile` |
### UI & Navigation
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.ShowProgress` | `message` | — | Shows loading overlay |
| `NanoflowCommons.HideProgress` | — | — | Hides loading overlay |
| `NanoflowCommons.ShowConfirmation` | `question`, `positiveButtonCaption`, `cancelButtonCaption` | Boolean | true = confirmed |
| `NanoflowCommons.NavigateTo` | `target` | — | Programmatic navigation |
| `NanoflowCommons.ToggleSidebar` | — | — | Show/hide sidebar |
### Local Storage
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.SetStorageItemString` | `key`, `value` | — | Persist string locally |
| `NanoflowCommons.GetStorageItemString` | `key` | String | Read stored string |
| `NanoflowCommons.SetStorageItemObject` | `key`, `value` | — | Persist Mendix object |
| `NanoflowCommons.GetStorageItemObject` | `key`, `entity` | Object | Read stored object |
| `NanoflowCommons.StorageItemExists` | `key` | Boolean | Check before reading |
| `NanoflowCommons.RemoveStorageItem` | `key` | — | Delete a stored item |
| `NanoflowCommons.ClearLocalStorage` | — | — | Clear everything |
### Object Utilities
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.GetGuid` | `object` | String | GUID of Mendix object |
| `NanoflowCommons.GetObjectByGuid` | `entity`, `guid` | Object | Retrieve by GUID |
| `NanoflowCommons.RefreshObject` | `object` | — | Refresh without page reload |
| `NanoflowCommons.RefreshEntity` | `entity` | — | Refresh all objects of type |
### Utilities
| Action | Parameters | Returns | Notes |
|--------|-----------|---------|-------|
| `NanoflowCommons.Wait` | `milliseconds` | — | Async delay |
| `NanoflowCommons.TimeBetween` | `startDate`, `endDate`, `unit` | Decimal | Time difference |
| `NanoflowCommons.GenerateUniqueID` | — | String | Session-scoped unique ID |
| `NanoflowCommons.Base64Encode` | `value` | String | Encode string to Base64 |
| `NanoflowCommons.Base64Decode` | `value` | String | Decode Base64 string |
---
## Disallowed Activities
These will produce validation errors:
- `RAISE ERROR` / `ErrorEvent` — not available in nanoflows
- `CALL JAVA ACTION` — Java actions cannot run client-side
- `EXECUTE DATABASE QUERY` — direct SQL requires server
- `CALL EXTERNAL ACTION` — external actions are server-side
- `SHOW HOME PAGE` — home page navigation is server-side
- `CALL REST SERVICE` / `SEND REST REQUEST` — REST calls are server-side
- `IMPORT FROM MAPPING` / `EXPORT TO MAPPING` — mapping operations are server-side
- `TRANSFORM JSON` — JSON transformations are server-side
- `DOWNLOAD FILE` — file downloads require server-side processing
### Nanoflow-only: SYNCHRONIZE
`SYNCHRONIZE` is the mirror image of the list above — it is allowed **only** in a
nanoflow, because offline synchronization is a client-side operation. The same
statement in a microflow fails the build with CE0009 "This action is not
supported in microflows."; mxcli reports it as **MDL057** before you get there.
```sql
synchronize all; -- the whole offline database
synchronize unsynchronized; -- only objects with uncommitted offline changes (Mendix 9.4+)
synchronize $Order, $Lines; -- named objects/lists ("Specific" mode)
synchronize all on error continue;
synchronize all on error without rollback {
log error 'sync failed';
};
```
The mode is always written out, including `all` — the statement says what it does
rather than relying on the reader knowing the platform default.
**Naming trap:** the variable form is stored as `Specific`, not `Selected`, even
though Studio Pro's UI calls it "Selected object(s)". Storage enums come from the
Model SDK's `SynchronizationType`, never from the UI wording.
- All **workflow actions** (11 types: CallWorkflow, OpenWorkflow, SetTaskOutcome, etc.)
## Return Type Restrictions
Binary return type is NOT allowed in nanoflows.
## Error Handling in Nanoflows
Since `RAISE ERROR` is forbidden, handle errors per-action with `ON ERROR`:
```mdl
$Location = CALL JAVASCRIPT ACTION NanoflowCommons.GetCurrentLocation (
timeout = 5000,
maximumAge = 0,
highAccuracy = false
) ON ERROR CONTINUE;
IF $Location = empty THEN
SHOW MESSAGE WARNING 'Could not get your location.';
RETURN;
END IF;
```
For per-action error handling without CONTINUE:
```mdl
$Result = CALL NANOFLOW Sales.NAV_Risky () ON ERROR ROLLBACK;
```
### Most activities take NO error handling in a nanoflow
An `ON ERROR` clause of **any** form is rejected on these six, with
**CE6035** "Error handling type is not supported" — measured on Mendix 11.14.0:
| Refused in a nanoflow | Accepted |
|---|---|
| `CHANGE`, `LOG`, `SHOW PAGE`, `CLOSE PAGE`, `SHOW MESSAGE`, `VALIDATION FEEDBACK` | `DECLARE`, `SET` (the two *variable* activities) |
mxcli refuses the clause rather than writing a nanoflow mxbuild rejects. The
split is by activity, not by "client-side vs server-side" — `SHOW MESSAGE` is as
client-side as it gets and still refuses one.
A nanoflow activity **aborts the flow** on error by default (a nanoflow has no
transaction to roll back), which is why there is nothing to configure. That
default is also why writing `Rollback` there is an error and not a no-op.
## Security (GRANT/REVOKE)
```mdl
GRANT EXECUTE ON NANOFLOW Shop.NAV_Filter TO Shop.User, Shop.Admin;
REVOKE EXECUTE ON NANOFLOW Shop.NAV_Filter FROM Shop.User;
```
## Management Commands
```mdl
SHOW NANOFLOWS
SHOW NANOFLOWS IN MyModule
DESCRIBE NANOFLOW MyModule.NAV_ShowDetails
DROP NANOFLOW MyModule.NAV_ShowDetails;
RENAME NANOFLOW MyModule.NAV_OldName TO NAV_NewName;
MOVE NANOFLOW Sales.NAV_OpenCart TO FOLDER 'UI/Navigation';
SHOW ACCESS ON NANOFLOW MyModule.NAV_ShowDetails;
```
## Common Mistakes
1. **Using Java actions** — Use CALL JAVASCRIPT ACTION instead.
2. **Using RAISE ERROR** — Nanoflows cannot raise errors directly. Handle per-action with ON ERROR or guard with IF checks.
3. **Expecting transactions** — Nanoflows have no automatic rollback. Design for idempotency.
4. **File operations** — DOWNLOAD FILE is server-only.
5. **Binary return types** — Not supported in nanoflows.
6. **REST/external calls** — REST calls and external actions are server-only. Call a microflow to do server work.
7. **Calling NanoflowCommons before checking availability** — Always check `StorageItemExists` before reading; always check `IsConnectedToServer` before syncing.
8. **Using SYNCHRONIZE outside native mobile context** — Only call SYNCHRONIZE in native mobile offline flows.
## Validation Checklist
- [ ] No RAISE ERROR / ErrorEvent
- [ ] No Java action calls
- [ ] No REST calls, external action calls, or database queries
- [ ] No file download operations
- [ ] No import/export mapping or JSON transformation
- [ ] No workflow actions
- [ ] No show home page
- [ ] No binary return type
- [ ] Parameters and return types are nanoflow-compatible
- [ ] JavaDoc documentation present
- [ ] NAV_ naming prefix used
- [ ] NanoflowCommons actions guarded (connectivity check, storage exists check)
- [ ] Progress indicators hidden on both success and error paths