--- 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 /src/components /src/ui ``` ### package.json ```json { "name": "", "widgetName": "", "version": "1.0.0", "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 ``` The `` 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/\.xml ```xml ``` The `id` attribute must be `..` — the second-to-last segment is the **lowercase** widget name, which becomes the JS subdirectory. This must match the `` 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 | `title` | | `boolean` | Toggle | Show/hide, enable | `show header` | | `integer` | Number | Counts, sizes | `columns` | | `decimal` | Decimal | Measurements | `Opacity` | | `enumeration` | Enum choice | Mode selection | See below | | `expression` | Dynamic value | Computed text | `label` | | `textTemplate` | Template text | Formatted text with params | See below | | `attribute` | Entity attribute | Data binding | See below | | `datasource` | List data source | Lists, grids | `data source` | | `widgets` | Child widgets | Content slots | `content` | | `action` | On-click action | Buttons, links | `on click` | | `icon` | Icon | Decorative | `icon` | | `image` | Image | Avatar, logo | `image` | | `object` | Compound | Complex config | See below | ### Enumeration Example ```xml Alignment left Center right ``` ### Attribute Binding Example ```xml value The attribute to display ``` ### TextTemplate Example ```xml display text default text ``` ### Object (Compound) Example — e.g. column definitions ```xml columns header column attribute width (px) ``` Note: `datasource="datasource"` links the attribute picker to the `datasource` property. ### Property Groups Use nested `` for Studio Pro tab organization: ```xml ``` ## Step 3: Write the Entry Component ### src/\.tsx ```tsx import { ReactElement } from "react"; import { ContainerProps } from "../typings/Props"; import { MyComponent } from "./components/MyComponent"; import "./ui/.css"; export function (props: ContainerProps): ReactElement { // map Mendix props to React component props return ; } ``` The `typings/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"; // 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 (

{title}

{value &&

{value}

}
); } ``` ## Step 5: Editor Config (optional but recommended) ### src/\.editorConfig.ts Controls how the widget appears in Studio Pro's design mode: ```ts import { PreviewProps } from "../typings/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: PreviewProps, defaultProperties: properties ): properties { return defaultProperties; } ``` ## Step 6: CSS Styles ### src/ui/\.css ```css .widget- { /* widget styles */ } ``` Use a `.widget-` prefix to avoid CSS collisions. ## Step 7: Build ```bash cd npm install npm run build ``` Output: `dist//com.example.widgets..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` - [ ] `` in package.xml matches `.xml` filename (without extension) - [ ] `` 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 `` - [ ] All `attribute` properties list valid `` 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 `` |