MendixWidgetGleam
Build Mendix Pluggable Widgets with Gleam — no JSX required.
Write React components entirely in Gleam and run them as Mendix Studio Pro widgets. React bindings are provided by redraw/redraw_dom, Mendix API bindings by mendraw, and build tools + JS interop by glendix.
Why Gleam?
- Static type safety — Gleam’s robust type system catches runtime errors at compile time
- Immutable data — Predictable state management
- JavaScript target support —
gleam build --target javascriptoutputs ES modules - glendix + mendraw packages — Type-safe Gleam bindings for React + Mendix API + JS Interop, supporting
EditableValue,ActionValue,ListValueand all other Mendix Pluggable Widget API types
Architecture
src/
mendix_widget_gleam.gleam # Main widget module
editor_config.gleam # Studio Pro property panel configuration
editor_preview.gleam # Studio Pro design view preview
components/
hello_world.gleam # Shared Hello World component
MendixWidget.xml # Widget property definitions
package.xml # Mendix package manifest
package.json # npm dependencies (React, external libraries, etc.)
gleam.toml # Includes glendix >= 4.0.3 + mendraw dependency
React/Mendix FFI and JS Interop bindings are not included in this project — they are provided by the glendix and mendraw Hex packages.
Build Pipeline
Widget code (.gleam) + glendix package (Hex)
| gleam run -m glendix/build (Gleam compilation runs automatically)
ES modules (.mjs) — build/dev/javascript/...
| Bridge JS (auto-generated) handles imports
| Rollup (pluggable-widgets-tools)
.mpk widget package — dist/
Core Principles
The Gleam function fn(JsProps) -> Element has an identical signature to a React functional component. React bindings are provided by redraw / redraw_dom, while mendraw provides Mendix runtime type accessors and glendix provides JS interop, so widget projects need only focus on business logic.
// src/mendix_widget_gleam.gleam
import mendraw/mendix.{type JsProps}
import redraw.{type Element}
import redraw/dom/attribute
import redraw/dom/html
pub fn widget(props: JsProps) -> Element {
let sample_text = mendix.get_string_prop(props, "sampleText")
html.div([attribute.class("widget-hello-world")], [
html.text("Hello " <> sample_text),
])
}
Mendix complex types can also be used in a type-safe manner from Gleam:
import mendraw/mendix
import mendraw/mendix/editable_value
import mendraw/mendix/action
pub fn widget(props: JsProps) -> Element {
// Access EditableValue
let name_attr: EditableValue = mendix.get_prop_required(props, "name")
let display = editable_value.display_value(name_attr)
// Execute ActionValue
let on_save: Option(ActionValue) = mendix.get_prop(props, "onSave")
action.execute_action(on_save)
// ...
}
Getting Started
Prerequisites
- Gleam (v1.0+)
- Node.js (v16+)
- Mendix Studio Pro (for widget testing)
Installation
gleam run -m glendix/install # Auto-downloads Gleam deps + installs npm deps + generates binding code (external React packages must be installed manually beforehand)
Build
gleam run -m glendix/build # Gleam compilation + widget build (.mpk output)
Build artefacts are output as .mpk files in the dist/ directory.
Development
gleam run -m glendix/dev # Gleam compilation + dev server (HMR, port 3000)
gleam run -m glendix/start # Linked development with Mendix test project
Commands
All commands are unified under gleam. gleam run -m automatically compiles Gleam before running the script.
| Command | Description |
|---|---|
gleam run -m glendix/install | Install dependencies (Gleam + npm) + generate binding code |
gleam run -m glendix/build | Production build (.mpk output) |
gleam run -m glendix/dev | Development server (HMR, port 3000) |
gleam run -m glendix/start | Linked development with Mendix test project |
gleam run -m glendix/lint | Run ESLint |
gleam run -m glendix/lint_fix | ESLint auto-fix |
gleam run -m glendix/release | Release build |
gleam run -m mendraw/marketplace | Search/download Mendix Marketplace widgets |
gleam run -m glendix/define | Widget property definition TUI editor |
gleam build --target javascript | Gleam to JS compilation only |
gleam test | Run Gleam tests |
gleam format | Format Gleam code |
Using External React Components
React component libraries distributed as npm packages can be used from pure Gleam without writing any .mjs FFI files.
Step 1: Install the npm package
npm install recharts
Step 2: Add bindings to gleam.toml
Add a [tools.glendix.bindings] section to your gleam.toml:
[tools.glendix.bindings.recharts]
components = ["PieChart", "Pie", "Cell", "Tooltip", "ResponsiveContainer"]
Step 3: Generate bindings
gleam run -m glendix/install
A binding_ffi.mjs file is generated automatically. It is also regenerated on subsequent builds via gleam run -m glendix/build.
Step 4: Use from Gleam
import glendix/binding
import mendraw/interop
import redraw.{type Element}
import redraw/dom/attribute.{type Attribute}
fn m() { binding.module("recharts") }
pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
}
pub fn tooltip(attrs: List(Attribute)) -> Element {
interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
}
External React components follow the same calling pattern as html.div.
Tech Stack
- Gleam — Widget logic and UI (compiled to JavaScript target)
- glendix — Build tools + JS Interop Gleam bindings (Hex package)
- mendraw — Mendix API Gleam bindings (Hex package)
- React 19 — Mendix Pluggable Widget runtime
- Rollup — Bundling via
@mendix/pluggable-widgets-tools
Limitations
- The Gleam to JS to Mendix Widget pipeline is not an officially supported combination; build configuration customisation may be required
- JSX files are not used — all React logic is implemented via Gleam + glendix
- React bindings use redraw/redraw_dom via glendix — other Gleam React libraries are not used
- FFI files are not written directly in the widget project — React/Mendix FFI is provided by glendix
Licence
Apache License 2.0 — see LICENSE