Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Create Custom Widget

ASecurity

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.

128 stars
0 votes
0 copies
0 views
Added 9/26/2026
ai-agentstypescriptgobashreactnodeexpressapi

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

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-code

Installs 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.

Security grade badge for Create Custom Widget
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mendixlabs-create-custom-widget/badge)](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.

Download with Pro
Files
SKILL.md
---
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>` |

Attribution

mendixlabsmendixlabs
View sourceSee grades on GitHubMore from mendixlabs →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698621 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →