| name | nushell-plugin-builder |
| description | 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. |
Nushell Plugin Builder
Overview
This skill helps create Nushell plugins in Rust. Plugins are standalone executables that extend Nushell with custom commands, data transformations, and integrations.
Quick Start
1. Create Plugin Project
cargo new nu_plugin_<name>
cd nu_plugin_<name>
cargo add nu-plugin nu-protocol
2. Basic Plugin Structure
use 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)
}
3. Build and Install
cargo build --release
cargo install --path . --locked
plugin add ~/.cargo/bin/nu_plugin_<name>
plugin use <name>
"hello" | my-command
Command Types
SimplePluginCommand
For commands that operate on single values:
- Input:
&Value
- Output:
Result<Value, LabeledError>
- Use for: transformations, simple filters, single value operations
PluginCommand
For commands that handle streams:
- Input:
PipelineData
- Output:
Result<PipelineData, LabeledError>
- Use for: streaming transformations, lazy processing, large datasets
See references/advanced-features.md for streaming examples.
Defining Command Signatures
Input-Output Types
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
Parameters
Signature::build("my-command")
.named("output", SyntaxShape::Filepath, "output file", Some('o'))
.switch("verbose", "enable verbose output", Some('v'))
.required("input", SyntaxShape::String, "input value")
.optional("count", SyntaxShape::Int, "repeat count")
.rest("files", SyntaxShape::Filepath, "files to process")
Accessing Arguments
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)?;
let count: Option<i64> = call.opt(1)?;
let files: Vec<String> = call.rest(2)?;
}
Error Handling
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.
Serialization
MsgPackSerializer (Recommended)
- Binary format, much faster
- Use for production plugins
JsonSerializer
- Text-based, human-readable
- Useful for debugging
Choose in main():
serve_plugin(&MyPlugin, MsgPackSerializer)
Common Patterns
String Transformation
Value::String { val, .. } => {
Ok(Value::string(val.to_uppercase(), call.head))
}
List Generation
let items = vec![
Value::string("a", call.head),
Value::string("b", call.head),
];
Ok(Value::list(items, call.head))
Record (Table Row)
use nu_protocol::record;
Ok(Value::record(
record! {
"name" => Value::string("example", call.head),
"size" => Value::int(42, call.head),
},
call.head,
))
Table (List of Records)
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:
- Filtering streams
- HTTP API calls
- File system operations
- Multi-command plugins
Development Workflow
Iterative Development
cargo build
plugin add target/debug/nu_plugin_<name>
plugin use <name>
"test" | my-command
plugin rm <name>
plugin add target/debug/nu_plugin_<name>
plugin use <name>
Automated Testing
[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.
Advanced Features
Streaming Data
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| );
Ok(PipelineData::ListStream(ListStream::new(filtered, span, None), None))
}
}
Engine Interaction
let home = engine.get_env_var("HOME")?;
engine.add_env_var("MY_VAR", Value::string("value", span))?;
let config = engine.get_plugin_config()?;
let cwd = engine.get_current_dir()?;
Custom Values
Define custom data types that extend beyond Nushell's built-in types. See references/advanced-features.md for complete guide.
Important Constraints
Stdio Restrictions
- Plugins cannot use stdin/stdout (reserved for protocol)
- Check
engine.is_using_stdio() before attempting stdio access
Path Handling
- Always use paths relative to
engine.get_current_dir()
- Never assume current working directory
Version Compatibility
- Match
nu-plugin and nu-protocol versions
- Both should match target Nushell version
Reference Documentation
references/plugin-protocol.md - Protocol details, serialization, lifecycle
references/advanced-features.md - Streaming, EngineInterface, custom values
references/examples.md - Complete working examples and patterns
references/testing-debugging.md - Development workflow, debugging, troubleshooting
External Resources
Template Script
Use 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.