Guide for creating Nushell plugins in Rust using nu_plugin and nu_protocol crates. Use when users want to build custom Nushell commands, extend Nushell with new functionality, create data transformations, or integrate external tools/APIs into Nushell. Covers project setup, command implementation, streaming data, custom values, and testing.
90
86%
Does it follow best practices?
Impact
95%
1.20xAverage score across 3 eval scenarios
Advisory
Suggest reviewing before use
This skill helps create Nushell plugins in Rust. Plugins are standalone executables that extend Nushell with custom commands, data transformations, and integrations.
cargo new nu_plugin_<name>
cd nu_plugin_<name>
cargo add nu-plugin nu-protocoluse nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{LabeledError, Signature, Type, Value};
struct MyPlugin;
impl Plugin for MyPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
vec![Box::new(MyCommand)]
}
}
struct MyCommand;
impl SimplePluginCommand for MyCommand {
type Plugin = MyPlugin;
fn name(&self) -> &str {
"my-command"
}
fn signature(&self) -> Signature {
Signature::build("my-command")
.input_output_type(Type::String, Type::Int)
}
fn run(
&self,
_plugin: &MyPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError> {
match input {
Value::String { val, .. } => {
Ok(Value::int(val.len() as i64, call.head))
}
_ => Err(LabeledError::new("Expected string input")
.with_label("requires string", call.head))
}
}
}
fn main() {
serve_plugin(&MyPlugin, MsgPackSerializer)
}# Build
cargo build --release
# Install to cargo bin
cargo install --path . --locked
# Register with nushell
plugin add ~/.cargo/bin/nu_plugin_<name> # Add .exe on Windows
plugin use <name>
# Test
"hello" | my-commandFor commands that operate on single values:
&ValueResult<Value, LabeledError>For commands that handle streams:
PipelineDataResult<PipelineData, LabeledError>See references/advanced-features.md for streaming examples.
use nu_protocol::{Signature, Type};
Signature::build("my-command")
.input_output_type(Type::String, Type::Int)Common types: String, Int, Float, Bool, List(Box<Type>), Record(...), Any
Signature::build("my-command")
// Named flags
.named("output", SyntaxShape::Filepath, "output file", Some('o'))
.switch("verbose", "enable verbose output", Some('v'))
// Positional arguments
.required("input", SyntaxShape::String, "input value")
.optional("count", SyntaxShape::Int, "repeat count")
.rest("files", SyntaxShape::Filepath, "files to process")fn run(&self, call: &EvaluatedCall, ...) -> Result<Value, LabeledError> {
let output: Option<String> = call.get_flag("output")?;
let verbose: bool = call.has_flag("verbose")?;
let input: String = call.req(0)?; // First positional
let count: Option<i64> = call.opt(1)?; // Second positional
let files: Vec<String> = call.rest(2)?; // Remaining args
}Always return LabeledError with span information:
Err(LabeledError::new("Error message")
.with_label("specific issue", call.head))This shows users exactly where the error occurred in their command.
MsgPackSerializer (Recommended)
JsonSerializer
Choose in main():
serve_plugin(&MyPlugin, MsgPackSerializer) // Production
// serve_plugin(&MyPlugin, JsonSerializer) // DebugValue::String { val, .. } => {
Ok(Value::string(val.to_uppercase(), call.head))
}let items = vec![
Value::string("a", call.head),
Value::string("b", call.head),
];
Ok(Value::list(items, call.head))use nu_protocol::record;
Ok(Value::record(
record! {
"name" => Value::string("example", call.head),
"size" => Value::int(42, call.head),
},
call.head,
))let records = vec![
Value::record(record! { "name" => Value::string("a", span) }, span),
Value::record(record! { "name" => Value::string("b", span) }, span),
];
Ok(Value::list(records, call.head))See references/examples.md for complete working examples including:
# Build
cargo build
# Test (debug build)
plugin add target/debug/nu_plugin_<name>
plugin use <name>
"test" | my-command
# After changes, reload
plugin rm <name>
plugin add target/debug/nu_plugin_<name>
plugin use <name>[dev-dependencies]
nu-plugin-test-support = "0.109.1"#[cfg(test)]
mod tests {
use nu_plugin_test_support::PluginTest;
#[test]
fn test_command() -> Result<(), nu_protocol::ShellError> {
PluginTest::new("myplugin", MyPlugin.into())?
.test_examples(&MyCommand)
}
}See references/testing-debugging.md for debugging techniques and troubleshooting.
For lazy processing of large datasets, use PipelineData:
impl PluginCommand for MyCommand {
fn run(&self, input: PipelineData, ...) -> Result<PipelineData, LabeledError> {
let filtered = input.into_iter().filter(|v| /* condition */);
Ok(PipelineData::ListStream(ListStream::new(filtered, span, None), None))
}
}// Get environment variables
let home = engine.get_env_var("HOME")?;
// Set environment variables (before response)
engine.add_env_var("MY_VAR", Value::string("value", span))?;
// Get plugin config from $env.config.plugins.<name>
let config = engine.get_plugin_config()?;
// Get current directory for path resolution
let cwd = engine.get_current_dir()?;Define custom data types that extend beyond Nushell's built-in types. See references/advanced-features.md for complete guide.
Stdio Restrictions
engine.is_using_stdio() before attempting stdio accessPath Handling
engine.get_current_dir()Version Compatibility
nu-plugin and nu-protocol versionsreferences/plugin-protocol.md - Protocol details, serialization, lifecyclereferences/advanced-features.md - Streaming, EngineInterface, custom valuesreferences/examples.md - Complete working examples and patternsreferences/testing-debugging.md - Development workflow, debugging, troubleshootingUse scripts/init_plugin.py to scaffold a new plugin with proper structure:
python3 scripts/init_plugin.py <plugin-name> [--output-dir <path>]This creates a complete working plugin template ready to customize.
aed1afb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.