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
Plugin Info : Metadata about the plugin (name, version, author)
Block Info : Metadata for each block the plugin provides
Execute Function : The logic that runs when the block executes
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
Create a new Rust library
cargo new --lib my_plugin
cd my_plugin
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"
Implement plugin exports
Create the required C-compatible functions in src/lib.rs.
Build the plugin
The DLL is at target/release/my_plugin.dll (Windows) or target/release/libmy_plugin.so (Linux).
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:
Plugin Version Author Blocks ExamplePlugin 0.1.0 Your Name 1 CFClearance 1.0.0 Community 2
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:
Find the block
Plugin blocks appear under their specified category (e.g., “Utilities”).
Drag to canvas
Add the block to your pipeline like any built-in block.
Configure settings
The settings panel shows fields defined in the plugin’s JSON schema.
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:
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
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
Build plugin: cargo build --release
Copy DLL to plugins/ folder
Launch IronBullet
Check Plugins panel for load status
Add block to pipeline
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