This is a template for creating plugins for the Nodle node editor. It demonstrates how to create custom nodes that can be dynamically loaded at runtime.
cargo build --releaseThe plugin will be built as a dynamic library:
- Linux:
target/release/libnodle_plugin_template.so - macOS:
target/release/libnodle_plugin_template.dylib - Windows:
target/release/nodle_plugin_template.dll
Copy the built library to one of these directories:
~/.nodle/plugins/(user plugins)./plugins/(local plugins)
This template includes two example nodes:
- Category: Utility
- Description: Simple greeting node with editable message
- Outputs: Message (String)
- Category: Math
- Description: Adds two numbers together
- Inputs: A (Float), B (Float)
- Outputs: Result (Float)
Every plugin must implement the NodePlugin trait:
impl NodePlugin for ExamplePlugin {
fn plugin_info(&self) -> PluginInfo { ... }
fn register_nodes(&self, registry: &mut dyn NodeRegistryTrait) { ... }
fn on_load(&self) -> Result<(), PluginError> { ... }
fn on_unload(&self) -> Result<(), PluginError> { ... }
}Each node type needs a factory that implements NodeFactory:
impl NodeFactory for HelloWorldNodeFactory {
fn metadata(&self) -> NodeMetadata { ... }
fn create_node(&self, position: Pos2) -> PluginNodeHandle { ... }
}The actual node logic implements PluginNode:
impl PluginNode for HelloWorldNode {
fn id(&self) -> String { ... }
fn position(&self) -> Pos2 { ... }
fn set_position(&mut self, position: Pos2) { ... }
fn get_parameter_ui(&self) -> ParameterUI { ... }
fn handle_ui_action(&mut self, action: UIAction) -> Vec<ParameterChange> { ... }
fn get_parameter(&self, name: &str) -> Option<NodeData> { ... }
fn set_parameter(&mut self, name: &str, value: NodeData) { ... }
fn process(&mut self, inputs: &HashMap<String, NodeData>) -> HashMap<String, NodeData> { ... }
}IMPORTANT: Use Safe Wrapper Types
The plugin must export these C functions using safe wrapper types:
#[no_mangle]
pub extern "C" fn create_plugin() -> PluginHandle {
PluginHandle::new(Box::new(ExamplePlugin))
}
#[no_mangle]
pub extern "C" fn destroy_plugin(handle: PluginHandle) {
let _ = unsafe { handle.into_plugin() };
}- Use the SDK: Always depend on
nodle-plugin-sdkfor interfaces - Use Safe Wrappers: Always use
PluginHandleandPluginNodeHandle- never pass trait objects directly - Data-Driven UI: Use
get_parameter_ui()andhandle_ui_action()instead of direct egui rendering - Node Metadata: Provide rich metadata for better integration
- Workspace Compatibility: Specify which workspaces support your nodes
- Error Handling: Use
PluginErrorfor consistent error reporting - Resource Management: Clean up properly in
on_unload()
READ THE PLUGIN_DEVELOPMENT.md FILE for detailed safety requirements. Key points:
- Never return
Box<dyn PluginNode>fromcreate_node()- usePluginNodeHandle - Never export
extern "C"functions that return trait objects directly - Always use the data-driven UI pattern with
get_parameter_ui()andhandle_ui_action() - Passing trait objects through FFI boundaries causes undefined behavior and crashes
To create your own plugin:
- Copy this template
- Update
Cargo.tomlwith your plugin name - Implement your
NodePluginstruct - Create your node factories and implementations
- Update the
register_nodes()method to register your nodes
Build and copy the plugin to test it:
cargo build --release
mkdir -p ~/.nodle/plugins
cp target/release/libnodle_plugin_template.dylib ~/.nodle/plugins/Then run Nodle - your nodes should appear in the appropriate workspace menus!