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.
Pro shows the line behind each finding and how to fix it
Scanned 9/26/2026
npx -y skills add mendixlabs/mxcli --skill create-custom-widget --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Create Custom Widget?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mendixlabs-create-custom-widget)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: create-custom-widget
description: "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."
---
# Create Custom Pluggable Widget
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.
```bash
mkdir -p <WidgetName>/src/components <WidgetName>/src/ui
```
### package.json
```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
```json
{
"extends": "./node_modules/@mendix/pluggable-widgets-tools/configs/tsconfig.base.json"
}
```
### src/package.xml
```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
<?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
```xml
<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
```xml
<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
```xml
<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
```xml
<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:
```xml
<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
```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
```tsx
// 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.
```tsx
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:
```ts
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
```css
.widget-<widget-name> {
/* widget styles */
}
```
Use a `.widget-<widget-name>` prefix to avoid CSS collisions.
## Step 7: Build
```bash
cd <widget-dir>
npm install
npm run build
```
Output: `dist/<version>/com.example.widgets.<WidgetName>.mpk`
## Step 8: Install to Mendix Project
```bash
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
- [ ] `id` in `.xml` matches `packagePath.WidgetName`
- [ ] `<name>` in package.xml matches `.xml` filename (without extension)
- [ ] `<file path>` in package.xml matches packagePath with `/` separators
- [ ] Entry `.tsx` exports a function with the exact widget name
- [ ] CSS file imported in entry `.tsx`
- [ ] `needsEntityContext` matches whether entity data is needed
- [ ] No manual `Props.d.ts` file (auto-generated by build tool)
- [ ] All `expression` properties have `<returnType>`
- [ ] All `attribute` properties list valid `<attributeType>` entries
- [ ] `object` properties with attributes set `datasource` reference
## 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>` |
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!