Building the Mendix Extensibility Playground
Creating a playground with a VS Code feel for testing and experimenting with Mendix extensions.
TL;DR: Built a Monaco editor inside Studio Pro to test the Web Extensibility API in real-time. Download it here to experiment instantly. Note: Currently requires manual save (Ctrl+S) - a new save mechanism that will persist automatically is coming.
The Problem
Testing Mendix Web Extensibility API calls was a slow, repetitive process:
- Write TypeScript code
- Build the extension (
npm run build) - Deploy to Studio Pro extensions folder
- Close Studio Pro completely
- Reopen Studio Pro
- Navigate to your extension
- Test your changes
- Find a bug
- Repeat all steps again
This took 2-3 minutes per iteration. For a simple API test, I spent more time waiting than coding.
I wanted a playground with VS Code’s instant feedback - where you can write code, press Ctrl+Enter, and immediately see the results.
The Solution: Extensibility Playground
An interactive Monaco-powered code editor that lives inside Studio Pro:
No build cycle. No restart. Just write → execute → see.
Think of it like the browser console, but for manipulating Mendix domain models, microflows, and pages.
Download
Requirements:
- Mendix Studio Pro 11.3 or higher
- Extensibility setting must be enabled (see instructions below)
Download the extension
Download extensibility-playground.zip and extract it to your project.
Extract to your project
Extract the zip file to your Mendix project’s extensions folder:
<YourMendixProject>/
└── extensions/
└── extensibility-playground/
├── main.js
├── ui.js
├── extensibility-playground.css
└── manifest.jsonExample path: C:\Users\YourName\Mendix\MyProject-main\extensions\extensibility-playground\
Enable extensibility (Mendix 11.3+)
If you haven’t enabled extensibility yet:
- In Studio Pro, click Edit → Preferences
- Go to the Advanced tab
- Under “Extension Development”, check “Start Studio Pro in Extension Development Mode. Requires restart of Studio Pro.”
- Click OK
- Restart Studio Pro for the changes to take effect
Load the extension
Option 1: Restart Studio Pro
- Close Studio Pro completely
- Reopen your project
Option 2: Synchronize App Directory (faster!)
- In Studio Pro, go to App → Synchronize App Directory
- Wait for synchronization to complete
Then:
- Go to Extensions → Extensibility Playground
- Start experimenting! 🎉
What Can You Do With It?
Example: Create an Entity in 30 Seconds
Let’s create a Customer entity with attributes:
const { projects, domainModels, microflows } = studioPro.app.model;
const domainModels_list = await domainModels.loadAll(
(info) => info.moduleName === 'MyFirstModule'
);
const domainModel = domainModels_list[0];
if (!domainModel) {
log('❌ Domain model not found');
return;
}
// Check if Customer entity already exists
let customer = domainModel.entities.find(e => e.name === 'Customer');
if (!customer) {
log('Creating Customer entity...');
// Create Customer entity with Name and Age attributes
customer = await domainModel.addEntity({
name: 'Customer',
attributes: [
{ name: 'Name', type: 'String' },
{ name: 'Age', type: 'Integer' }
]
});
customer.dataStorageGuid = crypto.randomUUID();
customer.location = { x: 100, y: 100 };
// Set dataStorageGuid for each attribute
customer.attributes.forEach(attr => {
attr.dataStorageGuid = crypto.randomUUID();
if (attr.type.$Type === 'DomainModels$StringAttributeType') {
attr.type.length = 200;
}
});
await domainModels.save(domainModel);
log('✅ Created Customer entity with Name and Age attributes');
} else {
log('✅ Customer entity already exists');
}Result: Press Ctrl+Enter to execute. The entity appears in Studio Pro instantly - remember to press Ctrl+S to persist!
Example: Building Project Structure
Create Folder
const { projects } = studioPro.app.model;
const module = await projects.getModule('MyFirstModule');
if (!module) {
log('❌ MyFirstModule not found');
return;
}
let objectsFolder = await projects.getFolder(module.$ID, 'Objects');
if (!objectsFolder) {
objectsFolder = await projects.addFolder(module.$ID, 'Objects');
log('✅ Created Objects folder');
} else {
log('✅ Objects folder already exists');
}Result: Learn the complete workflow - from simple folder creation to a full Customer entity with microflow!
About Persistence 💾
Currently, the playground (and all Web Extensibility API extensions) requires manual save (Ctrl+S) to persist changes.
Coming in Mendix 11.5: Automatic programmatic persistence will be available! The API will support saving changes without manual intervention.
How It Works Now
const { domainModels } = studioPro.app.model;
// Load domain model
const [domainModel] = await domainModels.loadAll(
(info) => info.moduleName === 'MyFirstModule'
);
// Create entity
const entity = await domainModel.addEntity({
name: 'Customer'
});
// Save to Studio Pro's in-memory model
await domainModels.save(domainModel);
// ⚠️ User must press Ctrl+S to persist
log('✅ Entity visible in Studio Pro');
log('⚠️ Press Ctrl+S to persist');Current behavior:
save()updates Studio Pro’s in-memory model ✅- Changes appear in UI immediately ✅
- Changes don’t persist until Ctrl+S ⚠️
- Restarting without Ctrl+S loses changes ❌
What’s coming:
- A new save mechanism will persist automatically ✅
- No more manual Ctrl+S required ✅
Patterns
Every model modification follows this flow:
const { domainModels } = studioPro.app.model;
// 1️⃣ Load the unit
const [domainModel] = await domainModels.loadAll(
(info) => info.moduleName === 'MyFirstModule'
);
// 2️⃣ Modify in memory
const entity = await domainModel.addEntity({
name: 'Product'
});
entity.dataStorageGuid = crypto.randomUUID();
// 3️⃣ Save to Studio Pro UI
await domainModels.save(domainModel);
// 4️⃣ User presses Ctrl+S to persist
log('⚠️ Press Ctrl+S to persist');The create() + Spread Pattern
When working with complex types like entity references or enumerations, use the create() + spread pattern:
const { microflows } = studioPro.app.model;
const [microflow] = await microflows.loadAll(...);
// ✅ CORRECT: create() + spread for entity types
microflow.microflowReturnType = {
...(await microflows.create('DataTypes$ObjectType')),
entity: 'MyFirstModule.Customer'
};
await microflows.save(microflow);Why this pattern?
This is currently a workaround. Mendix is working behind the scenes to add proper parameters to the create() functions for instantiating elements correctly. For now, you need to use create() + spread to set properties like entity.
// ❌ WRONG - Options silently ignored
const type = await microflows.createElement('DataTypes$ObjectType', {
entity: 'Module.Entity' // Silently ignored!
});
// ✅ WORKAROUND - Use create() + spread for now
const type = {
...(await microflows.create('DataTypes$ObjectType')),
entity: 'Module.Entity' // Works with this pattern
};Note: This will be improved in future Mendix releases when proper instantiation parameters are added to the API.
Always Generate GUIDs
Critical: Never forget to generate dataStorageGuid for entities and attributes!
const entity = await domainModel.addEntity({
name: 'Product'
});
entity.dataStorageGuid = crypto.randomUUID(); // ✅ CRITICAL
// Also for attributes
entity.attributes.forEach(attr => {
attr.dataStorageGuid = crypto.randomUUID();
});Why this matters:
- These GUIDs map to actual database table identifiers
- Without them, you get database conflicts
What’s Inside the Playground
The playground is more than just a code editor:
Monaco Editor Features
- ✅ Multi-cursor editing (Alt+Click)
- ✅ Keyboard shortcuts (Ctrl+Enter to execute)
- ✅ Syntax highlighting for JavaScript/TypeScript
Execution Environment
// Global objects available:
studioPro.app.model.domainModels // Domain models
studioPro.app.model.microflows // Microflows
studioPro.app.model.pages // Pages
studioPro.app.model.projects // Folders & modules
log(message) // Console output
crypto.randomUUID() // GUID generationWhen to Use the Playground 🎯
✅ Perfect Use Cases
The playground excels at:
- Learning the Web Extensibility API - Instant feedback loop for experimentation
- Prototyping extension ideas - Test concepts in seconds, not hours
- Debugging API behaviors - See exactly what the API does
- Teaching Mendix development - Live demonstrations with immediate results
- Testing model patterns - Try different approaches quickly
❌ Not Ideal For
The playground specifically is not ideal for:
- Production automation - Use Web Extensibility API in a proper extension (not playground)
- Complex workflows - Build structured extensions with proper architecture
Note: The Web Extensibility API itself can be used for automation - just not through the playground editor. Build proper extensions for production use cases.
Architecture
The playground creates an AsyncFunction with access to the studioPro global, executes it directly in Studio Pro, and captures console output.
Technology stack:
- React 18 + TypeScript for UI
- Monaco Editor (VS Code engine)
- Vite for build system
- Tailwind CSS for styling
Troubleshooting 🔧
Changes Disappear
Changes disappear after restart
Problem: Created entities vanish when reopening Studio Pro.
Root Cause: You forgot to press Ctrl+S after running the code.
Solution:
const entity = await domainModel.addEntity({ name: 'Customer' });
entity.dataStorageGuid = crypto.randomUUID();
await domainModels.save(domainModel);
log('✅ Changes in Studio Pro UI');
log('⚠️ Press Ctrl+S to persist to disk!');Remember: The API doesn’t persist automatically yet - a new save mechanism that will persist automatically is coming.
Resources
- Mendix Extensions API Reference
- Mendix Web Extensibility API - Getting Started
- Monaco Editor Documentation
Have questions? Find me on LinkedIn or send me an email at b.thomsin@gmail.com. I’d love to hear what you build with the playground!