Blog

Jacob Beers

June 15, 2026

Spread the word


Share your thoughts

One of the most unusual parts of MatchBox is the ESP32 target.

The MatchBox open beta is available at https://github.com/ortus-boxlang/matchbox, and it can compile BoxLang scripts into bytecode and deploy them to ESP32 microcontrollers. That means the same language used for scripts, native tools, web services, and browser logic can also run on a small embedded device.

This is very much beta territory, but it is also one of the clearest examples of why MatchBox exists. A Rust-based BoxLang VM opens deployment targets that would not make sense for the JVM runtime.


🧠 How It Works

The ESP32 target cross-compiles a specialized MatchBox runner for Xtensa or RISC-V architectures, compiles your .bxs script to .bxb bytecode using the Postcard serialization format (for 64-bit to 32-bit architecture compatibility), and writes the bytecode to a dedicated flash partition.

┌──────────────────────────────────────────────────────────────────┐
│                    ESP32 Build Pipeline                           │
│                                                                   │
│   app.bxs                                                         │
│      │                                                            │
│      │  matchbox --target esp32 --chip esp32s3                    │
│      ▼                                                            │
│  ┌─────────────────────────────────────────────────────────┐     │
│  │  Compile to .bxb bytecode (Postcard format)             │     │
│  │  64-bit → 32-bit architecture compatible                │     │
│  └───────────────────────┬─────────────────────────────────┘     │
│                          │                                        │
│             ┌────────────┴────────────┐                          │
│             ▼                         ▼                           │
│  ┌─────────────────────┐  ┌───────────────────────────┐          │
│  │  Full Flash         │  │  Fast Deploy (default)    │          │
│  │  --full-flash       │  │  --flash                  │          │
│  │                     │  │                           │          │
│  │  Installs runner    │  │  Updates bytecode only    │          │
│  │  firmware +         │  │  ~1 second, no firmware   │          │
│  │  partition layout   │  │  rebuild required         │          │
│  └─────────────────────┘  └───────────────────────────┘          │
└──────────────────────────────────────────────────────────────────┘

At runtime, the ESP32 runner starts a dedicated FreeRTOS task with a 48KB stack to host the MatchBox VM. The BoxLang server scope is automatically populated with hardware information - for example, server.os.arch returns xtensa or riscv depending on the chip - so scripts can inspect the environment they are running in.


⚡ Three Deployment Modes

First Deploy: Full Flash

The first time you flash a device, you must perform a full flash. This installs the MatchBox Runner firmware and the custom partition table required for BoxLang.

matchbox app.bxs --target esp32 --chip esp32s3 --full-flash

This sets up a 1MB storage partition at offset 0x110000 reserved exclusively for BoxLang bytecode. The firmware and VM together add roughly 800KB to 1.2MB to the firmware image.

Subsequent Updates: Fast Deploy

Once the runner is on the device, you only need to update the bytecode. This takes about one second and does not rebuild or reflash the firmware.

matchbox app.bxs --target esp32 --chip esp32s3 --flash

Live Development: Watch Mode

Watch mode gives embedded development a much tighter feedback loop than the usual full firmware rebuild cycle:

matchbox app.bxs --target esp32 --chip esp32s3 --watch
┌──────────────────────────────────────────────────────────────────┐
│                    Watch Mode Cycle                               │
│                                                                   │
│   Save .bxs file                                                  │
│        │                                                          │
│        ▼                                                          │
│   Kill espflash monitor                                           │
│        │                                                          │
│        ▼                                                          │
│   Flash updated bytecode (~1 second)                              │
│        │                                                          │
│        ▼                                                          │
│   Restart monitor + hardware reset                                │
│        │                                                          │
│        └──────────────────────────────► Output on serial console  │
└──────────────────────────────────────────────────────────────────┘

Standard output maps to the serial console, so you can monitor the device with espflash monitor.


📟 What BoxLang Looks Like on a Device

A basic ESP32 script is ordinary BoxLang:

println( "Hello from ESP32!" )

for ( i = 1; i <= 5; i++ ) {
    println( "Loop " & i )
    sleep( 1000 )
}

No special syntax, no embedded-specific API to learn for basic scripting. The language is the same; the runtime underneath is tailored for the hardware.


🌐 Embedded Web Direction

ESP32 does not run the full native MatchBox app server - that would be the wrong shape for a microcontroller. Instead, MatchBox is moving toward a strict embedded web profile: route-driven code with lightweight request and response helpers.

import boxlang.web

app = web.server()

app.get( "/", ( event, rc, prc ) -> {
    event.renderHtml( "<h1>Hello from MatchBox</h1>" )
} )

app.get( "/status", ( event, rc, prc ) -> {
    event.renderJson( { "ok": true } )
} )

To opt into this build flavor:

matchbox app.bxs --target esp32 --chip esp32s3 --esp32-web --full-flash

The embedded profile intentionally rejects heavier server features at compile time. The following are explicitly excluded:

┌────────────────────────────────────────────────────────────┐
│          ESP32 Web Profile: Feature Boundaries             │
├──────────────────────────────┬─────────────────────────────┤
│ ✅ Supported                 │ ❌ Not supported             │
├──────────────────────────────┼─────────────────────────────┤
│ GET / POST route handlers    │ app.listen()                │
│ renderHtml / renderJson      │ Filesystem-backed templates │
│ Lightweight request/response │ Static asset mounts         │
│ Status endpoints             │ Webhook helpers             │
│ server scope (arch info)     │ Cookies and sessions        │
└──────────────────────────────┴─────────────────────────────┘

That early compile-time rejection is intentional. It prevents firmware from implying support for features that are too heavy or not implemented on the device.


🔧 Hardware Features Roadmap

The ESP32 runner is being shaped as a bundled embedded platform with feature-gated capabilities. The project already defines platform feature directions across several hardware domains:

┌──────────────────────────────────────────────────────────────────┐
│                  ESP32 Platform Feature Directions                │
│                                                                   │
│   Network          │  Connectivity    │  Hardware I/O             │
│   ─────────────    │  ─────────────   │  ─────────────            │
│   Web routes       │  Bluetooth       │  GPIO pins                │
│   mDNS             │  WiFi            │  I2C / SPI                │
│                    │                  │  SD card                  │
│                    │                  │  PSRAM                    │
│                    │                  │  Camera                   │
│                    │                  │  Printer support          │
└──────────────────────────────────────────────────────────────────┘

For direct hardware access, the current direction is Native Fusion and BoxLang modules that wrap hardware services. On embedded systems this means being careful with allocations, dependency choices, and panic behavior - Rust code that is fine on a desktop may be a poor fit for ESP32 if it expects OS features or large memory.


🛠️ Setup Requirements

ESP32 development requires more host setup than native or browser MatchBox targets. You need:

  • Rust ESP32 toolchain installed with espup
  • Activated ESP-IDF environment
  • espflash 3.3.0 or newer
  • ldproxy
  • Standard ESP-IDF build prerequisites: Python, CMake, Ninja, and C tooling
# Install espup and the ESP32 Rust toolchain
cargo install espup
espup install

# Activate ESP-IDF (run in every shell before building)
source /path/to/esp-idf/export.sh
export RUSTUP_TOOLCHAIN=esp

Then run MatchBox from that same shell.

WSL users: You must attach your USB device to the Linux instance using usbipd-win. From a Windows Administrator PowerShell:

usbipd list
usbipd attach --busid <BUSID> --auto-attach

To build just the ESP32 runner for a specific chip:

cd crates/matchbox-esp32-runner
rustup run esp cargo build --release --target xtensa-esp32s3-espidf

⚠️ Beta Boundaries: What to Know

The ESP32 target is where beta expectations matter most.

┌──────────────────────────────────────────────────────────────────┐
│              ESP32 Memory and Capability Overview                 │
├─────────────────────────┬────────────────────────────────────────┤
│ SRAM                    │ Usually 520KB - be mindful of large    │
│                         │ arrays and deep recursion              │
├─────────────────────────┼────────────────────────────────────────┤
│ Flash (VM + runtime)    │ 800KB - 1.2MB added to firmware size  │
├─────────────────────────┼────────────────────────────────────────┤
│ Bytecode partition      │ 1MB storage at offset 0x110000         │
├─────────────────────────┼────────────────────────────────────────┤
│ FreeRTOS VM stack       │ 48KB dedicated task stack              │
├─────────────────────────┼────────────────────────────────────────┤
│ Java interop            │ ❌ Not available                        │
├─────────────────────────┼────────────────────────────────────────┤
│ DOM / js.* APIs         │ ❌ Not available                        │
├─────────────────────────┼────────────────────────────────────────┤
│ Native Fusion (Rust)    │ ✅ Available via hardware modules       │
└─────────────────────────┴────────────────────────────────────────┘

Some chips and feature combinations may require local runner builds. USB flashing may require OS-specific permissions. Large arrays, deep recursion, heavy modules, and broad dynamic behavior are not good fits for a microcontroller profile.

This target is for experimentation, feedback, and early embedded applications today.


🚀 Where to Start

Start with the ESP32 hello example in the repo. Get a full flash working, then try fast bytecode deploys.

# Step 1: First flash - installs firmware and partition layout
matchbox hello.bxs --target esp32 --chip esp32s3 --full-flash

# Step 2: Iterate fast - only updates bytecode (~1 second)
matchbox hello.bxs --target esp32 --chip esp32s3 --flash

# Step 3: Live development loop
matchbox hello.bxs --target esp32 --chip esp32s3 --watch

After that, experiment with a small route-driven status endpoint or a simple sensor/control loop using the embedded web profile.

MatchBox on ESP32 is not about pretending a microcontroller is a server. It is about bringing a productive language to small hardware while preserving the ability to drop into Rust where the device needs it.


🔭 The Bigger Picture

┌──────────────────────────────────────────────────────────────────┐
│                    BoxLang Runtime Landscape                       │
│                                                                   │
│  ┌───────────────────────┐    ┌─────────────────────────────┐    │
│  │   BoxLang JVM Runtime │    │     MatchBox (Rust VM)      │    │
│  │                       │    │                             │    │
│  │  - Web Servers        │    │  - Browser (WASM/JS)        │    │
│  │  - AWS Lambda         │    │  - WASI Containers          │    │
│  │  - Google Cloud Fns   │    │  - Edge Runtimes            │    │
│  │  - Desktop (Electron) │    │  - Native CLI               │    │
│  │  - Full Java interop  │    │  - ESP32 / IoT  ◄── Here   │    │
│  │  - All BL modules     │    │  - Static deployments       │    │
│  └───────────────────────┘    └─────────────────────────────┘    │
│                                                                   │
│           Same BoxLang language. Different runtimes.              │
└──────────────────────────────────────────────────────────────────┘

That is a compelling direction for BoxLang: scripts, native apps, web services, browsers, edge workloads, and now physical devices. The language stays the same. Where it runs is a deployment decision, not a language boundary.

Explore the project and open issues at https://github.com/ortus-boxlang/matchbox.

Read the full ESP32 docs at https://boxlang.ortusbooks.com/getting-started/running-boxlang/esp32.


Join the Ortus Community

Be part of the movement shaping the future of web development. Stay connected and receive the latest updates on product launches, tool updates, promo services and much more.

Subscribe to our newsletter for exclusive content.

Follow Us on Social media and don't miss any news and updates:

Add Your Comment

Recent Entries