WebAssembly (WASM) modules extend Mule’s capabilities by allowing you to write custom workflow steps in Go (or other languages that compile to WASM). WASM modules run in a sandboxed environment with access to host functions provided by Mule.

Overview

WASM modules in Mule provide:

  • Sandboxed execution - Run custom code safely within workflows
  • Host function access - Communicate with the outside world through Mule’s APIs
  • JSON I/O - Simple input/output via stdin/stdout using JSON
  • Performance - Near-native execution speed
  • Portability - Compile once, run anywhere (WASM is platform-independent)

How WASM Modules Work

Input/Output Pattern

WASM modules receive configuration and data via stdin as JSON, process it, and output results to stdout as JSON:

// Input structure
type Input struct {
    Prompt string                 `json:"prompt"`
    Data   map[string]interface{} `json:"data,omitempty"`
}

// Output structure
type Output struct {
    Result  string                 `json:"result"`
    Data    map[string]interface{} `json:"data,omitempty"`
    Success bool                   `json:"success"`
}

func main() {
    decoder := json.NewDecoder(os.Stdin)
    var input Input
    decoder.Decode(&input)
    
    // Process input...
    
    output := Output{
        Result:  "processed: " + input.Prompt,
        Success: true,
    }
    
    encoder := json.NewEncoder(os.Stdout)
    encoder.Encode(output)
}

Host Functions

WASM modules access host capabilities through imported functions. Mule provides the following host functions:

HTTP Requests

//go:wasmimport env http_request_with_headers
func http_request_with_headers(methodPtr, methodSize, urlPtr, urlSize, bodyPtr, bodySize, headersPtr, headersSize uint32) uint32

//go:wasmimport env get_last_response_body
func get_last_response_body(bufferPtr, bufferSize uint32) uint32

//go:wasmimport env get_last_response_status
func get_last_response_status() uint32

Workflow & Agent Execution

//go:wasmimport env trigger_workflow_or_agent
func trigger_workflow_or_agent(operationTypePtr, operationTypeSize, idPtr, idSize, paramsPtr, paramsSize uint32) uint32

//go:wasmimport env execute_target
func execute_target(operationTypePtr, operationTypeSize, idPtr, idSize, paramsPtr, paramsSize uint32) uint32

//go:wasmimport env wait_for_job_and_get_output
func wait_for_job_and_get_output(jobIDPtr, jobIDSize, bufferPtr, bufferSize uint32) uint32

Bash Command Execution

//go:wasmimport env execute_bash_command
func execute_bash_command(commandPtr, commandSize, workingDirPtr, workingDirSize uint32) uint32

Git Operations

//go:wasmimport env create_git_branch
func create_git_branch(branchNamePtr, branchNameSize, baseBranchPtr, baseBranchSize uint32) uint32

//go:wasmimport env push_git_branch
func push_git_branch(remotePtr, remoteSize, branchNamePtr, branchNameSize, forcePtr uint32) uint32

//go:wasmimport env create_git_worktree
func create_git_worktree(pathPtr, pathSize, branchPtr, branchSize, commitPtr, commitSize uint32) uint32

Working Directory Management

//go:wasmimport env get_working_directory
func get_working_directory(bufferPtr, bufferSize uint32) uint32

//go:wasmimport env set_working_directory
func set_working_directory(pathPtr, pathSize uint32) uint32

//go:wasmimport env get_current_branch
func get_current_branch(bufferPtr, bufferSize uint32) uint32

Result Retrieval

//go:wasmimport env get_last_operation_result
func get_last_operation_result(bufferPtr, bufferSize uint32) uint32

//go:wasmimport env get_last_operation_status
func get_last_operation_status() uint32

Building WASM Modules

Prerequisites

  • Go 1.25+ or TinyGo
  • Basic understanding of Go programming

Using Standard Go

GOOS=wasip1 GOARCH=wasm go build -o module.wasm main.go

Using TinyGo (for smaller binaries)

tinygo build -o module.wasm -target wasi main.go

Example: Simple Text Processor

Create a module that transforms text:

// main.go
package main

import (
    "encoding/json"
    "fmt"
    "os"
    "strings"
)

type Input struct {
    Text     string `json:"text"`
    Transform string `json:"transform"` // uppercase, lowercase, titlecase
}

type Output struct {
    Result  string `json:"result"`
    Success bool   `json:"success"`
}

func main() {
    var input Input
    json.NewDecoder(os.Stdin).Decode(&input)
    
    var result string
    switch input.Transform {
    case "uppercase":
        result = strings.ToUpper(input.Text)
    case "lowercase":
        result = strings.ToLower(input.Text)
    case "titlecase":
        result = strings.Title(input.Text)
    default:
        result = input.Text
    }
    
    json.NewEncoder(os.Stdout).Encode(Output{
        Result:  result,
        Success: true,
    })
}

Build and test locally:

GOOS=wasip1 GOARCH=wasm go build -o text-transform.wasm main.go
wasmtime text-transform.wasm

Available Example Modules

Mule includes several example WASM modules in examples/wasm/:

Text Processing

ModuleDescription
text-processorSimple text transformation (uppercase, etc.)
advanced-processorConfigurable text operations
pirate-transformTransform text to pirate speak
jq-filterApply jq filters to JSON data

HTTP Operations

ModuleDescription
http-requestMake basic HTTP GET/POST requests
http-request-with-headersHTTP requests with custom headers

GitHub Integration

ModuleDescription
github-issuesFetch and filter GitHub issues
issues-to-markdownConvert issues to formatted Markdown
github-commentPost comments to GitHub issues
create-pull-requestCreate GitHub pull requests
git-branch-pushPush git branches to remote
git-worktreeManage git worktrees for parallel development
issue-state-trackerTrack and update issue states

Workflow Control

ModuleDescription
execute-targetExecute named targets in workflows
run-default-workflowTrigger the default workflow
array-workflow-launcherLaunch multiple workflows from array data
spawn-multi-workflowSpawn multiple workflows in parallel
workflow-aggregatorAggregate results from multiple workflows
workflow_agent_demoTrigger workflows or agents from WASM

Validation

ModuleDescription
validation-moduleRun validation commands with retry logic

Using WASM Modules in Workflows

1. Build the Module

cd examples/wasm/text-processor
GOOS=wasip1 GOARCH=wasm go build -o text-processor.wasm main.go

2. Upload to Mule

Encode the binary and upload via API:

# Encode to base64
BASE64_WASM=$(base64 -w 0 text-processor.wasm)

# Upload via API
curl -X POST http://localhost:8080/api/v1/wasm-modules \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"text-processor\",
    \"description\": \"Transform text to uppercase, lowercase, or title case\",
    \"module_data\": \"$BASE64_WASM\",
    \"config\": {
      \"input_schema\": {
        \"text\": \"string\",
        \"transform\": \"string\"
      }
    }
  }"

3. Create a Workflow Step

# Create workflow with WASM step
curl -X POST http://localhost:8080/api/v1/workflows \
  -H "Content-Type: application/json" \
  -d '{
    "name": "text-processing-pipeline",
    "description": "Process text through multiple transformations"
  }'

WORKFLOW_ID="workflow-uuid"

# Add WASM module step
curl -X POST http://localhost:8080/api/v1/workflows/${WORKFLOW_ID}/steps \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wasm_module",
    "wasm_module_id": "wasm-module-uuid",
    "config": {
      "transform": "uppercase"
    }
  }'

4. Execute the Workflow

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "workflow/text-processing-pipeline",
    "messages": [
      {"role": "user", "content": "Transform this text: hello world"}
    ]
  }'

Complete Workflow Example

Here’s a complete example using the GitHub issues fetcher:

{
  "name": "github-issue-analysis",
  "description": "Fetch GitHub issues and analyze them",
  "is_async": false,
  "steps": [
    {
      "step_order": 1,
      "type": "wasm_module",
      "wasm_module_id": "github-issues-uuid",
      "config": {
        "url": "https://api.github.com/repos/owner/repo/issues",
        "state": "open",
        "labels": ["bug", "help wanted"]
      }
    },
    {
      "step_order": 2,
      "type": "wasm_module",
      "wasm_module_id": "issues-to-markdown-uuid",
      "config": {
        "format": "detailed"
      }
    },
    {
      "step_order": 3,
      "type": "agent",
      "agent_id": "analysis-agent-uuid",
      "config": {
        "task": "Analyze the following GitHub issues and identify common themes:\n{{step_2.output}}"
      }
    }
  ]
}

Best Practices

Input Validation

Always validate inputs in your WASM module:

func main() {
    var input Input
    if err := json.NewDecoder(os.Stdin).Decode(&input); err != nil {
        outputError(fmt.Errorf("failed to decode input: %w", err))
        return
    }
    
    if input.Text == "" {
        outputError(fmt.Errorf("text field is required"))
        return
    }
    // Process...
}

Error Handling

Return meaningful error messages:

func outputError(err error) {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}

Memory Management

Be careful with memory pointers in WASM:

// Keep references to byte slices to prevent garbage collection
commandBytes := []byte(command)
commandPtr := uint32(uintptr(unsafe.Pointer(&commandBytes[0])))
// Important: keep reference to prevent GC
_ = commandBytes

Testing Locally

Test with wasmtime before deploying:

# Create test input
echo '{"text": "hello world", "transform": "uppercase"}' | wasmtime module.wasm

Binary Size

For production, consider using TinyGo for smaller binaries:

# Standard Go: ~2-3MB
GOOS=wasip1 GOARCH=wasm go build -o module.wasm main.go

# TinyGo: ~100-500KB
tinygo build -o module.wasm -target wasi main.go

Hot Reload

Mule supports hot reloading of WASM modules. After uploading a new version, workflows will automatically use the updated module without restart. This is useful for development and iterative improvements.

Limitations

  • WASM modules run in a sandboxed environment
  • No direct file system access (use bash or filesystem tools)
  • No network access directly (use HTTP host functions)
  • Limited memory compared to native execution
  • Fixed timeout (controlled by workflow timeout settings)

Next Steps

Was this page helpful?