Skip to main content
The plugin system allows you to create custom blocks in Rust that integrate seamlessly with IronBullet’s visual editor and execution engine.

Overview

Plugins are compiled as DLL files (Windows) or shared objects (Linux) that expose blocks with custom logic. They appear in the block palette alongside built-in blocks and can be used in pipelines just like native blocks.

Plugin Architecture

Plugin Components

  1. Plugin Info: Metadata about the plugin (name, version, author)
  2. Block Info: Metadata for each block the plugin provides
  3. Execute Function: The logic that runs when the block executes
  4. Settings Schema: JSON schema defining block configuration options

Plugin ABI

Plugins communicate with IronBullet through a C-compatible ABI:
#[repr(C)]
pub struct PluginInfo {
    pub name: *const c_char,
    pub version: *const c_char,
    pub author: *const c_char,
    pub description: *const c_char,
    pub block_count: u32,
}

#[repr(C)]
pub struct BlockInfo {
    pub block_type_name: *const c_char,  // "PluginName.BlockName"
    pub label: *const c_char,            // Display name
    pub category: *const c_char,         // Palette category
    pub color: *const c_char,            // Hex color (#9b59b6)
    pub icon: *const c_char,             // Icon name
    pub settings_schema_json: *const c_char,
    pub default_settings_json: *const c_char,
}

#[repr(C)]
pub struct ExecuteResult {
    pub success: bool,
    pub updated_variables_json: *const c_char,
    pub log_message: *const c_char,
    pub error_message: *const c_char,
}

Creating a Plugin

1

Create a new Rust library

cargo new --lib my_plugin
cd my_plugin
2

Configure Cargo.toml

[package]
name = "my_plugin"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
3

Implement plugin exports

Create the required C-compatible functions in src/lib.rs.
4

Build the plugin

cargo build --release
The DLL is at target/release/my_plugin.dll (Windows) or target/release/libmy_plugin.so (Linux).
5

Load in IronBullet

Copy the DLL to the plugins/ folder next to the IronBullet executable.

Example Plugin

Here’s a complete example that creates a “Reverse String” block:
use std::collections::HashMap;
use std::ffi::{c_char, CStr, CString};
use std::sync::OnceLock;

// ── ABI structs ──

#[repr(C)]
pub struct PluginInfo {
    pub name: *const c_char,
    pub version: *const c_char,
    pub author: *const c_char,
    pub description: *const c_char,
    pub block_count: u32,
}

#[repr(C)]
pub struct BlockInfo {
    pub block_type_name: *const c_char,
    pub label: *const c_char,
    pub category: *const c_char,
    pub color: *const c_char,
    pub icon: *const c_char,
    pub settings_schema_json: *const c_char,
    pub default_settings_json: *const c_char,
}

#[repr(C)]
pub struct ExecuteResult {
    pub success: bool,
    pub updated_variables_json: *const c_char,
    pub log_message: *const c_char,
    pub error_message: *const c_char,
}

// ── Plugin metadata ──

static PLUGIN_INFO: OnceLock<PluginInfo> = OnceLock::new();
static BLOCK_INFO: OnceLock<BlockInfo> = OnceLock::new();

fn leak_cstring(s: &str) -> *const c_char {
    CString::new(s).unwrap().into_raw() as *const c_char
}

fn get_plugin_info() -> &'static PluginInfo {
    PLUGIN_INFO.get_or_init(|| PluginInfo {
        name: leak_cstring("ExamplePlugin"),
        version: leak_cstring("0.1.0"),
        author: leak_cstring("Your Name"),
        description: leak_cstring("Example plugin that reverses strings"),
        block_count: 1,
    })
}

fn get_block_info() -> &'static BlockInfo {
    BLOCK_INFO.get_or_init(|| BlockInfo {
        block_type_name: leak_cstring("ExamplePlugin.ReverseString"),
        label: leak_cstring("Reverse String"),
        category: leak_cstring("Utilities"),
        color: leak_cstring("#9b59b6"),
        icon: leak_cstring("repeat"),
        settings_schema_json: leak_cstring(
            r#"{"type":"object","properties":{"input_var":{"type":"string","title":"Input Variable","default":"data.SOURCE"}}}"#
        ),
        default_settings_json: leak_cstring(r#"{"input_var":"data.SOURCE"}"#),
    })
}

// ── Exports ──

#[no_mangle]
pub extern "C" fn plugin_info() -> *const PluginInfo {
    get_plugin_info() as *const PluginInfo
}

#[no_mangle]
pub extern "C" fn plugin_block_info(index: u32) -> *const BlockInfo {
    if index == 0 {
        get_block_info() as *const BlockInfo
    } else {
        std::ptr::null()
    }
}

#[no_mangle]
pub extern "C" fn plugin_execute(
    _block_index: u32,
    settings_json: *const c_char,
    variables_json: *const c_char,
) -> *const ExecuteResult {
    // Parse settings
    let settings_str = unsafe { CStr::from_ptr(settings_json).to_string_lossy() };
    let settings: HashMap<String, String> = 
        serde_json::from_str(&settings_str).unwrap_or_default();
    let input_var = settings.get("input_var")
        .cloned()
        .unwrap_or_else(|| "data.SOURCE".to_string());

    // Parse variables
    let vars_str = unsafe { CStr::from_ptr(variables_json).to_string_lossy() };
    let vars: HashMap<String, String> = 
        serde_json::from_str(&vars_str).unwrap_or_default();
    let input_value = vars.get(&input_var).cloned().unwrap_or_default();

    // Reverse the string
    let reversed: String = input_value.chars().rev().collect();

    // Build updated variables
    let mut updated = vars.clone();
    updated.insert("PLUGIN_RESULT".to_string(), reversed.clone());

    let updated_json = serde_json::to_string(&updated).unwrap();
    let log_msg = format!("Reversed '{}' → '{}'", input_value, reversed);

    let result = Box::new(ExecuteResult {
        success: true,
        updated_variables_json: CString::new(updated_json).unwrap().into_raw(),
        log_message: CString::new(log_msg).unwrap().into_raw(),
        error_message: std::ptr::null(),
    });

    Box::into_raw(result) as *const ExecuteResult
}

#[no_mangle]
pub extern "C" fn plugin_free_string(ptr: *const c_char) {
    if !ptr.is_null() {
        unsafe { drop(CString::from_raw(ptr as *mut c_char)); }
    }
}

Loading Plugins

Plugin Directory

IronBullet scans the plugins/ directory on startup:
ironbullet.exe
plugins/
  my_plugin.dll
  another_plugin.dll
  cf-clearance.dll

Hot-Loading

Plugins are loaded when:
  • IronBullet starts
  • You click “Reload Plugins” in the Plugins panel
  • You manually scan a directory via IPC
Plugins can be added or updated while IronBullet is running. Click Reload Plugins to refresh without restarting.

Plugin Manager Panel

View loaded plugins in the Plugins panel:
PluginVersionAuthorBlocks
ExamplePlugin0.1.0Your Name1
CFClearance1.0.0Community2
Click a plugin to see:
  • Full description
  • List of blocks it provides
  • Load status (loaded/error)

Using Plugin Blocks

Once loaded, plugin blocks appear in the block palette:
1

Find the block

Plugin blocks appear under their specified category (e.g., “Utilities”).
2

Drag to canvas

Add the block to your pipeline like any built-in block.
3

Configure settings

The settings panel shows fields defined in the plugin’s JSON schema.
4

Test in debug mode

Press F5 to test the plugin block with sample data.

Block Configuration Schema

Define block settings using JSON Schema:
{
  "type": "object",
  "properties": {
    "input_var": {
      "type": "string",
      "title": "Input Variable",
      "description": "Variable to reverse",
      "default": "data.SOURCE"
    },
    "output_var": {
      "type": "string",
      "title": "Output Variable",
      "default": "REVERSED"
    },
    "uppercase": {
      "type": "boolean",
      "title": "Convert to Uppercase",
      "default": false
    }
  },
  "required": ["input_var"]
}
Supported field types:
  • string - Text input
  • boolean - Checkbox
  • number - Numeric input
  • integer - Integer input
  • enum - Dropdown select

Variables System

Plugins receive and update variables:

Input Variables

let vars: HashMap<String, String> = serde_json::from_str(&vars_json)?;
let username = vars.get("USER").unwrap_or(&String::new());
let source = vars.get("data.SOURCE").unwrap_or(&String::new());

Output Variables

let mut updated = vars.clone();
updated.insert("RESULT".to_string(), result_value);
updated.insert("STATUS".to_string(), "success".to_string());

let json = serde_json::to_string(&updated)?;

Capture Variables

To mark a variable as a capture (shown in hits):
// The frontend handles capture marking based on block settings
// Just update the variable normally:
updated.insert("TOKEN".to_string(), extracted_token);

Error Handling

Return errors gracefully:
if input_value.is_empty() {
    let result = Box::new(ExecuteResult {
        success: false,
        updated_variables_json: CString::new("{}").unwrap().into_raw(),
        log_message: std::ptr::null(),
        error_message: CString::new("Input variable is empty").unwrap().into_raw(),
    });
    return Box::into_raw(result) as *const ExecuteResult;
}
The error message is displayed in the debugger and logged.

Available Plugins

Official plugins included with IronBullet:

cf-clearance

Cloudflare bypass using browser automation: Blocks:
  • CF Challenge Solver: Solves Cloudflare challenges
  • CF Cookie Extractor: Extracts clearance cookies
Settings:
  • Target URL
  • Browser path
  • Timeout

Best Practices

  • Avoid blocking I/O in execute functions
  • Cache compiled regexes or parsers
  • Use efficient data structures
  • Minimize allocations in hot paths
  • Provide clear, actionable error messages
  • Include context about what went wrong
  • Suggest fixes when possible
  • Don’t expose sensitive data in errors
  • Use UPPERCASE for output variables
  • Prefix with plugin name if generic (e.g., MYPLUGIN_RESULT)
  • Document expected input variables
  • Don’t overwrite USER, PASS, SOURCE unless intentional
  • Provide sensible defaults
  • Add descriptions for all fields
  • Validate inputs in execute function
  • Use enums for predefined choices

Debugging Plugins

Testing Locally

  1. Build plugin: cargo build --release
  2. Copy DLL to plugins/ folder
  3. Launch IronBullet
  4. Check Plugins panel for load status
  5. Add block to pipeline
  6. Test with Debug Mode (F5)

Logging

Plugins can’t directly print to console, but they can:
  • Return verbose log messages in ExecuteResult.log_message
  • Write to files for debugging
  • Use breakpoints with a debugger attached to IronBullet

Common Issues

Plugin not loading: Verify it’s in plugins/ folder and has .dll extension (Windows).Crash on load: Ensure all exports are extern "C" with #[no_mangle].Settings not appearing: Check JSON schema is valid and properly escaped.

Next Steps