Nushell Code Style Guide
Contents
| File | Topic |
|---|---|
| This file | Quick reference tables, do/don't checklists |
| patterns.md | Pipeline composition, command examples, code structure |
| formatting.md | Topiary conventions, spacing, declarations |
| debugging.md | --ide-check for agents, diagnostic parsing |
| nuon.md | NUON format, data serialization, config files |
| testing.md | nutest framework, snapshots, coverage |
| toolkit.md | toolkit.nu, repo utilities, commit conventions |
Agent Tip: Syntax Checking
nu --ide-check 10 file.nu | nu --stdin -c 'lines | each { from json } | where type == "diagnostic" | to json --raw'
Runs from bash (as agents do). --stdin is required for the second nu to read piped input. See debugging.md for parsing diagnostics inside nushell.
Conciseness for Advanced Users
Write code that an experienced nushell user can quickly apprehend. Leverage implicit features:
| Verbose | Concise | Why |
|---|---|---|
update field {|row| $row.field | str upcase} | update field { str upcase } | Closure receives field value directly |
each {|x| $x | str trim} | each { str trim } | $in implicit, pipeline flows |
where {|row| $row.status == "active"} | where status == "active" | where has field shorthand |
$data | each { $in | process } | $data | each { process } | $in passed automatically to first command |
Principle: If an advanced user knows how update, each, where work, they shouldn't need to parse redundant variable declarations.
Command Choices
| Task | Preferred | Avoid |
|---|---|---|
| Filtering | where | filter, each {if} | compact |
| List filtering | where $it =~ ... | where { $in =~ ... } |
| Parallel with order | par-each --keep-order | par-each (when order matters) |
| Pattern dispatch | match expression | Long if/else if chains |
| Record iteration | items {|k v| ...} | Manual key extraction |
| Table grouping | group-by ... --to-table | Manual grouping |
| Line joining | str join (char nl) | to text (context dependent) |
| Syntax check (human) | nu -c 'open file.nu | nu-check' | source file.nu |
| Syntax check (agent) | nu --ide-check 10 file.nu | nu-check (unstructured) |
| Membership | in operator | Multiple or conditions |
| Field extraction | get --optional | each {$in.field?} | compact |
| Negation | $x !~ ... | not ($x =~ ...) |
Pipeline Principles
Leading |
Place | at start of continuation lines, aligned with let.
Omit $in |
When body starts with pipeline command (each, where, select), input flows automatically.
Empty { } Pass-Through
Use empty { } for the branch that should pass through unchanged:
- •
| if $cond { transform } else { }— transform when true, pass through when false - •
| if $cond { } else { transform }— pass through when true, transform when false
Stateful Transforms
Use scan for sequences with state: use std/iter scan
→ See patterns.md for detailed examples.
Script CLI Pattern
For toolkit-style scripts with subcommands (like nu toolkit.nu test):
# toolkit.nu
export def main [] { } # Entry point (required, even if empty)
export def 'main test' [--json] {
# nu toolkit.nu test
}
export def 'main build' [] {
# nu toolkit.nu build
}
Key points:
- •
def main []— entry point when runningnu script.nu - •
def 'main subcommand' []— definesnu script.nu subcommand - •Must define
mainfor subcommands to be accessible - •Use
export defif script is also used as a module
Execution differs between script mode and module mode:
# Script mode: `main` is stripped, subcommands are top-level nu toolkit.nu # runs main nu toolkit.nu test # runs 'main test' nu toolkit.nu test --json # with flags
# Module mode: `main` stays in the command name use toolkit.nu toolkit # runs main toolkit main test # runs 'main test' — note `main` is required toolkit main test --json
→ See Nushell Scripts docs
Quick Reference
Do
- •Omit
$in |when command body starts with pipeline command - •Start continuation lines with
| - •Use empty
else { }for pass-through - •Use
matchfor type dispatch - •Use
infor membership testing - •Use
get --optionalfor field extraction - •Use
scanfor stateful transforms - •Use
wherefor filtering - •Use
where $it =~ ...for list filtering - •Combine consecutive
eachclosures when operations can be piped - •Define data first, then filter
- •Include type signatures:
]: input -> output { - •Use
@exampleattributes (nutest) - •Use
constfor static data - •Keep custom commands focused
- •Export ALL commands from implementation files (enables testing helpers)
- •Control public API via
mod.nure-exports (not by removing exports) - •Use
par-each --keep-orderfor parallel with deterministic output
Don't
- •Start command bodies with
$in |when a pipeline command follows - •Use spread operator
...with conditionals (use data-first +where) - •Wrap external commands in unnecessary parentheses
- •Over-extract helpers for one-time use
- •Create wrapper commands that just call an existing command
- •Use verbose names for local variables
- •Break the pipeline flow unnecessarily
- •Remove existing comments (preserve user's context)
- •Remove
exportfrom helpers to "make them private" (use mod.nu instead)
Formatting Summary
- •Run
topiary format <file>when available — it is the canonical formatter - •Empty blocks:
{ }with space - •Closures:
{ expr }with spaces - •Flags:
--flag (-f)with space - •Records: multi-line, no trailing comma
- •Variables:
let x =(no$on left)
→ See formatting.md for full conventions.