Skip to Content
ProjectsExtensibility PlaygroundOverview

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:

  1. Write TypeScript code
  2. Build the extension (npm run build)
  3. Deploy to Studio Pro extensions folder
  4. Close Studio Pro completely
  5. Reopen Studio Pro
  6. Navigate to your extension
  7. Test your changes
  8. Find a bug
  9. 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.json

Example path: C:\Users\YourName\Mendix\MyProject-main\extensions\extensibility-playground\

Enable extensibility (Mendix 11.3+)

If you haven’t enabled extensibility yet:

  1. In Studio Pro, click EditPreferences
  2. Go to the Advanced tab
  3. Under “Extension Development”, check “Start Studio Pro in Extension Development Mode. Requires restart of Studio Pro.”
  4. Click OK
  5. Restart Studio Pro for the changes to take effect

Load the extension

Option 1: Restart Studio Pro

  1. Close Studio Pro completely
  2. Reopen your project

Option 2: Synchronize App Directory (faster!)

  1. In Studio Pro, go to AppSynchronize App Directory
  2. Wait for synchronization to complete

Then:

  • Go to ExtensionsExtensibility Playground
  • Start experimenting! 🎉

What Can You Do With It?

Example: Create an Entity in 30 Seconds

Let’s create a Customer entity with attributes:

create-customer.js
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.js
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

current-pattern.js
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:

load-modify-save.js
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:

create-spread-pattern.js
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!

generate-guids.js
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 generation

When 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 after restart

Problem: Created entities vanish when reopening Studio Pro.

Root Cause: You forgot to press Ctrl+S after running the code.

Solution:

remember-save.js
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


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!