Create Custom Pluggable Widget
SkillDev toolsLets your agent create a Claude skill that builds custom Mendix widgets with React and TypeScript.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Create Custom Pluggable Widget skill
About this capability
Build a Mendix pluggable widget from scratch with React and TypeScript and package it as an .mpk. Use when no marketplace or built-in widget covers what is needed and a custom React component has to be written.
What this skill tells your AI
The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/create-custom-widget/SKILL.md and read by ahel’s review.
Build a Mendix pluggable widget from scratch using React + TypeScript. Produces a .mpk file ready for Studio Pro.
Prerequisites
- Node.js >= 16
- npm
Step 1: Scaffold the Project
Create a directory and generate all source files. Use PascalCase for the widget name.
mkdir -p <WidgetName>/src/components <WidgetName>/src/ui
package.json
{
"name": "<widget-name>",
"widgetName": "<WidgetName>",
"version": "1.0.0",
"description": "<description>",
"license": "Apache-2.0",
"config": {
"projectPath": "./tests/testProject",
"mendixHost": "http://localhost:8080",
"developmentPort": 3000
},
"packagePath": "com.example.widgets",
"scripts": {
"dev": "pluggable-widgets-tools start:web",
"build": "pluggable-widgets-tools build:web",
"lint": "pluggable-widgets-tools lint",
"lint:fix": "pluggable-widgets-tools lint:fix"
},
"devDependencies": {
"@mendix/pluggable-widgets-tools": "^11.6.0",
"@types/big.js": "^6.0.2"
},
"dependencies": {
"classnames": "^2.2.6"
},
"resolutions": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0"
},
"overrides": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0"
}
}
Naming rules:
name: kebab-case (npm package name)widgetName: PascalCase (matches .xml and .tsx filename)packagePath: reverse domain, dot-separated (e.g.com.example.widgets)
tsconfig.json
{
"extends": "./node_modules/@mendix/pluggable-widgets-tools/configs/tsconfig.base.json"
}
src/package.xml
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="<WidgetName>" version="1.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="<WidgetName>.xml"/>
</widgetFiles>
<files>
<file path="com/example/widgets/<widgetname>"/>
</files>
</clientModule>
</package>
The <file path> must match packagePath + lowercase widget name, with dots replaced by /. For example, for HelloWorld with packagePath=com.example.widgets, the path is com/example/widgets/helloworld.
Step 2: Define Widget Properties (widget.xml)
src/<WidgetName>.xml
<?xml version="1.0" encoding="utf-8"?>
<widget id="com.example.widgets.<widgetname>.<WidgetName>"
pluginWidget="true"
needsEntityContext="true"
offlineCapable="true"
supportedPlatform="Web"
xmlns="http://www.mendix.com/widget/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../node_modules/mendix/custom_widget.xsd">
<name><widget Name></name>
<description><description></description>
<icon/>
<properties>
<propertyGroup caption="General">
<!-- Add properties here -->
</propertyGroup>
</properties>
</widget>
The id attribute must be <packagePath>.<widgetname>.<WidgetName> — the second-to-last segment is the lowercase widget name, which becomes the JS subdirectory. This must match the <file path> in package.xml.
Set needsEntityContext="true" when the widget needs entity data. Set to "false" for standalone widgets.
Property Type Reference
| XML Type | Mendix Type | Use Case | Example |
|---|---|---|---|
string | Static text | Labels, titles | <property key="title" type="string"><caption>title</caption></property> |
boolean | Toggle | Show/hide, enable | <property key="showHeader" type="boolean" defaultValue="true"><caption>show header</caption></property> |
integer | Number | Counts, sizes | <property key="columns" type="integer" defaultValue="3"><caption>columns</caption></property> |
decimal | Decimal | Measurements | <property key="opacity" type="decimal" defaultValue="1.0"><caption>Opacity</caption></property> |
enumeration | Enum choice | Mode selection | See below |
expression | Dynamic value | Computed text | <property key="label" type="expression" defaultValue=""><caption>label</caption><returnType type="string"/></property> |
textTemplate | Template text | Formatted text with params | See below |
attribute | Entity attribute | Data binding | See below |
datasource | List data source | Lists, grids | <property key="datasource" type="datasource" isList="true"><caption>data source</caption></property> |
widgets | Child widgets | Content slots | <property key="content" type="widgets" required="false"><caption>content</caption></property> |
action | On-click action | Buttons, links | <property key="onclick" type="action"><caption>on click</caption></property> |
icon | Icon | Decorative | <property key="icon" type="icon" required="false"><caption>icon</caption></property> |
image | Image | Avatar, logo | <property key="image" type="image" required="false"><caption>image</caption></property> |
object | Compound | Complex config | See below |
Enumeration Example
<property key="alignment" type="enumeration" defaultValue="center">
<caption>Alignment</caption>
<description/>
<enumerationValues>
<enumerationValue key="left">left</enumerationValue>
<enumerationValue key="center">Center</enumerationValue>
<enumerationValue key="right">right</enumerationValue>
</enumerationValues>
</property>
Attribute Binding Example
<property key="value" type="attribute">
<caption>value</caption>
<description>The attribute to display</description>
<attributeTypes>
<attributeType name="string"/>
<attributeType name="integer"/>
<attributeType name="decimal"/>
</attributeTypes>
</property>
TextTemplate Example
<property key="displayText" type="textTemplate">
<caption>display text</caption>
<description/>
<translations>
<translation lang="en_US">default text</translation>
</translations>
</property>
Object (Compound) Example — e.g. column definitions
<property key="columns" type="object" isList="true">
<caption>columns</caption>
<description/>
<properties>
<propertyGroup caption="column">
<property key="header" type="textTemplate">
<caption>header</caption>
<translations><translation lang="en_US">column</translation></translations>
</property>
<property key="attribute" type="attribute" datasource="datasource">
<caption>attribute</caption>
<attributeTypes>
<attributeType name="string"/>
<attributeType name="integer"/>
</attributeTypes>
</property>
<property key="width" type="integer" defaultValue="100">
<caption>width (px)</caption>
</property>
</propertyGroup>
</properties>
</property>
Note: datasource="datasource" links the attribute picker to the datasource property.
Property Groups
Use nested <propertyGroup> for Studio Pro tab organization:
<properties>
<propertyGroup caption="General">
<!-- main properties -->
</propertyGroup>
<propertyGroup caption="Appearance">
<!-- style properties -->
</propertyGroup>
<propertyGroup caption="events">
<!-- action properties -->
</propertyGroup>
</properties>
Step 3: Write the Entry Component
src/<WidgetName>.tsx
import { ReactElement } from "react";
import { <WidgetName>ContainerProps } from "../typings/<WidgetName>Props";
import { MyComponent } from "./components/MyComponent";
import "./ui/<WidgetName>.css";
export function <WidgetName>(props: <WidgetName>ContainerProps): ReactElement {
// map Mendix props to React component props
return <MyComponent {...relevantProps} />;
}
The typings/<WidgetName>Props.d.ts file is auto-generated by the build tool from the .xml definition. Do NOT create it manually.
Key Mendix Prop Patterns
// string property
props.title // string
// boolean property
props.showHeader // boolean
// expression property
props.label?.value // string | undefined (use .value to get resolved text)
// attribute property (read)
props.value?.displayValue // string
props.value?.value // actual typed value
// attribute property (write)
props.value?.setValue(newValue)
// TextTemplate property
props.displayText?.value // string (resolved template)
// action property
props.onClick?.canExecute // boolean
props.onClick?.execute() // trigger the action
// datasource property
props.dataSource?.items // ObjectItem[] | undefined
props.dataSource?.status // "available" | "loading"
// widgets property (content slot)
props.content // ReactNode
// icon property
import { icon } from "mendix/components/web/icon";
<icon icon={props.icon} />
// object list property (e.g. columns)
props.columns // Array<{ header, attribute, width }>
// access attribute value for a specific item:
props.columns[0].attribute?.get(item)?.displayValue
Step 4: Write the React Component
src/components/MyComponent.tsx
Keep the component pure React — no Mendix API dependencies. This makes it testable and reusable.
import { ReactElement } from "react";
import classNames from "classnames";
export interface MyComponentProps {
title: string;
value?: string;
className?: string;
}
export function MyComponent({ title, value, className }: MyComponentProps): ReactElement {
return (
<div className={classNames("widget-my-component", className)}>
<h3>{title}</h3>
{value && <p>{value}</p>}
</div>
);
}
Step 5: Editor Config (optional but recommended)
src/<WidgetName>.editorConfig.ts
Controls how the widget appears in Studio Pro's design mode:
import { <WidgetName>PreviewProps } from "../typings/<WidgetName>Props";
export type properties = PropertyGroup[];
type PropertyGroup = {
caption: string;
propertyGroups?: PropertyGroup[];
properties?: Property[];
};
type Property = {
key: string;
caption: string;
description?: string;
};
export function getProperties(
_values: <WidgetName>PreviewProps,
defaultProperties: properties
): properties {
return defaultProperties;
}
Step 6: CSS Styles
src/ui/<WidgetName>.css
.widget-<widget-name> {
/* widget styles */
}
Use a .widget-<widget-name> prefix to avoid CSS collisions.
Step 7: Build
cd <widget-dir>
npm install
npm run build
Output: dist/<version>/com.example.widgets.<WidgetName>.mpk
Step 8: Install to Mendix Project
cp dist/*/*.mpk /path/to/mendix-project/widgets/
Then open/reload the project in Studio Pro.
Common Widget Patterns
KPI Card
Properties: title (string), value (expression/string), icon (icon), trend (enumeration: up/down/neutral), onclick (action)
Chart Wrapper
Properties: datasource (datasource), valueAttr (attribute/decimal), labelAttr (attribute/string), chartType (enumeration), height (integer)
Wrap a charting library (Chart.js, Recharts) inside the component.
Custom Input
Properties: value (attribute/string, writable), placeholder (string), onchange (action), validation (expression/string)
Set needsEntityContext="true". Use props.value.setValue() for two-way binding.
Layout Component
Properties: content (widgets), columns (integer), gap (integer)
Set needsEntityContext="false". Render children via {props.content}.
Checklist Before Build
-
idin.xmlmatchespackagePath.WidgetName -
<name>in package.xml matches.xmlfilename (without extension) -
<file path>in package.xml matches packagePath with/separators - Entry
.tsxexports a function with the exact widget name - CSS file imported in entry
.tsx -
needsEntityContextmatches whether entity data is needed - No manual
Props.d.tsfile (auto-generated by build tool) - All
expressionproperties have<returnType> - All
attributeproperties list valid<attributeType>entries -
objectproperties with attributes setdatasourcereference
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Cannot find module '../typings/...' | Haven't built yet | Run npm run build first, types are generated |
widget not showing in Studio Pro | Wrong id in XML | Ensure id="packagePath.WidgetName" |
CE0463 widget definition changed | Property mismatch | Ensure XML and component props match |
pluginWidget must be true | Missing attribute | Add pluginWidget="true" to <widget> |
Signals
- GitHub stars
- 122
- Forks
- 49
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
create-custom-widget- Source
- github.com/mendixlabs/mxcli