32blogby Studio Mitsu

Starship: The Minimal, Fast, Customizable Shell Prompt

Starship is the minimal, blazing-fast Rust prompt for any shell. Configure starship.toml, apply presets, and add custom modules that fit your workflow.

by omitsu19 min read
On this page

Starship is a single Rust binary that replaces the prompt of every shell on your machine — bash, zsh, fish, PowerShell, Nushell, Cmd — with one configurable, blazing-fast prompt driven by a single TOML file (~/.config/starship.toml). The win is concrete: a context-aware prompt that shows the git branch, language version, kubernetes context, and command duration without waiting on subshells, so it stays instant even on huge monorepos. Install it with one curl, add a single eval line to your shell rc file, and starship.toml becomes the only place you ever edit your prompt again — across every machine, every shell, every project.

You already know the pain. You wrote a perfect bash PS1 once in 2018, copied it across every dotfiles repo, and now half of it doesn't render in zsh, the git status block adds 400 ms to every command, and the colors are wrong on the new MacBook because the terminal is using a different color scheme. Then you start working with someone whose prompt shows the AWS account, the kubernetes namespace, and the Python venv all on one line, and you find yourself searching for "how to make a fancy bash prompt" at midnight.

Starship is the answer most developers settle on. It is a Rust binary that runs in around 1 ms per render, supports every major shell from one config, ships with 100+ context-aware modules out of the box, and stores its entire configuration in ~/.config/starship.toml. This article walks through installing it, understanding the default modules, customizing starship.toml step by step, applying presets like Gruvbox Rainbow and Tokyo Night, and writing your own custom modules. By the end, you will have a single prompt config you can drop into any machine.

Why starship instead of pure bash, oh-my-zsh, or powerlevel10k

Bash's PS1, oh-my-zsh themes, and zsh's powerlevel10k all exist for good reasons. Starship's case for replacing them comes down to four practical wins.

  1. One config, every shell. A single ~/.config/starship.toml works in bash, zsh, fish, PowerShell, Cmd, Nushell, Xonsh, Ion, and Elvish. If you ever switch shells — or your colleagues use a different one — your prompt follows you with zero rewrites.
  2. Real performance, not the appearance of it. Powerlevel10k earned its reputation with the "instant prompt" trick (showing a stale prompt while the real one renders in the background). Starship is just fast enough that it doesn't need that trick — a render typically completes in 1–10 ms because every module is implemented in Rust and runs in parallel inside one binary, with no subshell forks.
  3. 100+ context-aware modules. Starship ships with built-in modules for git, 40+ language runtimes (Node.js, Python, Rust, Go, Java, Kotlin, Ruby, PHP, Dart, Deno, Bun, Zig, Nim, OCaml, Crystal, Elixir, Erlang, Haskell, Lua, Perl, Scala, Swift, Typst, etc.), cloud platforms (AWS, Azure, GCP, OpenStack), DevOps tooling (Docker context, Kubernetes context, Helm, Terraform, Pulumi), and system info (battery, time, OS, hostname). They auto-activate based on the files in your current directory — no manual on/off switches.
  4. Sane defaults. Run starship init once and the default prompt is already useful: directory, git branch, git status, runtime version, command duration, and a colored chevron prompt symbol. You can ship the install on a fresh machine and have a working prompt in 30 seconds.

The implementation language is Rust (starship/starship) — and unlike the previous article in this series (gh CLI, which is Go), Rust is the right call here because every millisecond counts. A prompt runs on every single shell command, so the cost of a bloated, GC-paused, fork-heavy implementation adds up fast. Starship's render time stays in the low single-digit milliseconds even when it has to read your .git/HEAD, parse package.json, and check for an active kubernetes context — all in parallel.

Install starship and wire it into your shell

Two steps: install the binary, then add one line to your shell rc file. The official installation page lists every platform; the short version is below.

powershell
# winget (built into Windows 11)
winget install --id Starship.Starship

# or Scoop
scoop install starship

# or Chocolatey
choco install starship

After installation, verify the binary:

bash
starship --version
# starship 1.24.2

Now wire it into your shell. Pick the snippet for the shell you actually use and append it to the rc file:

bash
# ~/.bashrc
eval "$(starship init bash)"

# ~/.zshrc
eval "$(starship init zsh)"

# ~/.config/fish/config.fish
starship init fish | source

# PowerShell ($PROFILE)
Invoke-Expression (&starship init powershell)

# Nushell ~/.config/nushell/config.nu — see `starship init nu --help`
mkdir ~/.cache/starship
starship init nu | save -f ~/.cache/starship/init.nu

Open a new shell and your prompt is already replaced. No starship.toml exists yet — that's intentional. The defaults are good enough that most users run for weeks before they touch a config file.

The default modules: what you actually see and why

Run a fresh starship init in any directory and the prompt looks roughly like this:

text
~/projects/32blog on  master [!?] via  v22.13.0
❯

Each segment is a module that auto-activates based on context. The defaults are deliberately minimal — Starship hides modules whose data is empty. Here is what the segments mean and the modules that produced them, in order.

SegmentModuleWhen it appearsWhat it shows
~/projects/32blogdirectoryAlwaysCurrent directory, truncated to 3 segments by default; shows the repo root in cyan
on mastergit_branchInside a git repoBranch name with a Nerd Font branch glyph
[!?]git_statusWhen the working tree has changesSymbols for staged (+), modified (!), untracked (?), conflicted (=), ahead/behind (⇡⇣), stashed ($)
via v22.13.0nodejs (and 25 others)When package.json / .nvmrc is presentDetected runtime and version
characterAlwaysGreen on success, red on the previous command's non-zero exit

The full default prompt format string is the special variable $all, which expands to all of Starship's hundred-odd modules in a fixed order. You can see the entire default by running:

bash
starship print-config --default

That command dumps the built-in defaults to stdout in TOML format. Copy what you want into your own ~/.config/starship.toml and tweak from there — that's the canonical way to start customizing.

A common misconception is that every module always runs. It doesn't. Each module has a "should I activate?" check that runs before it produces any output, and Starship runs them all in parallel. The nodejs module looks for package.json, .node-version, .nvmrc, or *.js files; the python module looks for requirements.txt, pyproject.toml, Pipfile, or *.py; the kubernetes module is disabled = true by default and has to be turned on explicitly. That is why the prompt stays fast — most modules produce no output and finish in microseconds.

Configuring starship.toml: format strings, modules, and palettes

~/.config/starship.toml is the only file you ever edit. The minimum useful config is just a few lines:

toml
# ~/.config/starship.toml
"$schema" = 'https://starship.rs/config-schema.json'

# Add a blank line before each prompt
add_newline = true

# Wait at most 500 ms for any single module
command_timeout = 500

# Custom prompt symbol per status
[character]
success_symbol = '[➜](bold green)'
error_symbol   = '[✗](bold red)'

# Show command duration only when commands take ≥ 2 s
[cmd_duration]
min_time = 2_000
format = 'took [$duration]($style) '
style = 'bold yellow'

Save it, open a new shell, and the changes apply. Starship watches the file and re-parses it on every prompt render — no daemon to restart.

How the format string works

The format option at the top level decides which modules render and in what order. The default is the magic variable $all. To customize the layout, write your own format string and reference modules with $module_name:

toml
format = """
[╭─](bold blue) $directory$git_branch$git_status$nodejs$python$rust
[╰─](bold blue)$character"""

Three syntax pieces matter:

  • $variable — expands to the named module's output (or empty if the module is inactive)
  • [text](style) — wraps literal text in a style; the style follows the same syntax as module styles (bold green, fg:#ff5f87, bg:blue underline)
  • (...) — a parenthesized group hides itself entirely if every variable inside is empty (so you don't get a stray via when there's no language module)

That last rule is the secret to a clean prompt: you can write (via [$symbol $version](bold cyan) ) and the whole via … chunk vanishes when there's no detected runtime.

Configuring individual modules

Each module gets its own table. The pattern is always the same: set format, symbol, style, and optionally disabled:

toml
[git_branch]
symbol = '🌱 '
style  = 'bold purple'
format = 'on [$symbol$branch]($style) '

[git_status]
format = '([\[$all_status$ahead_behind\]]($style) )'
style  = 'bold red'
ahead       = '⇡${count}'
behind      = '⇣${count}'
diverged    = '⇡${ahead_count}⇣${behind_count}'
conflicted  = '='
modified    = '!'
staged      = '+'
untracked   = '?'

[directory]
truncation_length = 3
truncate_to_repo  = true
read_only         = ' 🔒'
style             = 'bold cyan'

[nodejs]
format = 'via [⬢ $version](bold green) '
detect_files = ['package.json', '.nvmrc', '.node-version']

[python]
symbol = '🐍 '
format = 'via [${symbol}${pyenv_prefix}(${version} )(\($virtualenv\) )]($style)'
style  = 'yellow bold'

[kubernetes]
disabled = false        # off by default — turn it on if you live in kubectl
symbol   = '☸ '
format   = 'on [$symbol$context( \($namespace\))]($style) '
style    = 'cyan bold'

The full reference is the config docs, which list every option for every module. The single most useful flag to remember is disabled = true|false — you can turn off any module you don't want without editing the format string.

Palettes: theming everything from one place

If you change colors directly inside every module, switching themes is a chore. Palettes solve that — define named colors once, then reference them everywhere:

toml
palette = 'catppuccin_mocha'

[palettes.catppuccin_mocha]
rosewater = '#f5e0dc'
flamingo  = '#f2cdcd'
pink      = '#f5c2e7'
mauve     = '#cba6f7'
red       = '#f38ba8'
peach     = '#fab387'
yellow    = '#f9e2af'
green     = '#a6e3a1'
teal      = '#94e2d5'
sky       = '#89dceb'
blue      = '#89b4fa'
lavender  = '#b4befe'
text      = '#cdd6f4'
surface0  = '#313244'
base      = '#1e1e2e'
mantle    = '#181825'
crust     = '#11111b'

[character]
success_symbol = '[❯](bold green)'
error_symbol   = '[❯](bold red)'

[git_branch]
style = 'bold mauve'

[directory]
style = 'bold blue'

Switching from Catppuccin Mocha to Tokyo Night is now a one-line change to the top-level palette setting. Define multiple palettes in the same file, swap between them with a single edit, and every module updates at once.

Presets: Tokyo Night, Gruvbox Rainbow, Pure, and friends

If writing your own config from scratch sounds tedious, Starship ships with a preset gallery — full starship.toml files for popular looks. Install one with a single command:

bash
# The 12 official presets, all installable the same way
starship preset nerd-font-symbols   -o ~/.config/starship.toml
starship preset no-nerd-font        -o ~/.config/starship.toml
starship preset bracketed-segments  -o ~/.config/starship.toml
starship preset plain-text-symbols  -o ~/.config/starship.toml
starship preset no-runtime-versions -o ~/.config/starship.toml
starship preset no-empty-icons      -o ~/.config/starship.toml
starship preset pure-preset         -o ~/.config/starship.toml
starship preset pastel-powerline    -o ~/.config/starship.toml
starship preset tokyo-night         -o ~/.config/starship.toml
starship preset gruvbox-rainbow     -o ~/.config/starship.toml
starship preset jetpack             -o ~/.config/starship.toml
starship preset catppuccin-powerline -o ~/.config/starship.toml

The four presets people actually pick:

  • tokyo-night — dark, cool blue-purple palette inspired by the tokyo-night-vscode-theme. The most popular dark preset on the Starship subreddit — clean rows, no powerline arrows.
  • gruvbox-rainbow — warm earth tones with powerline-style segment arrows. The reigning eye-candy preset, used in dozens of dotfiles screenshots on r/unixporn.
  • pure-preset — minimalist single-line prompt that mimics the JavaScript Pure prompt. Best if you find icons noisy and want a Spartan look.
  • pastel-powerline — pastel powerline segments with path substitution (replaces ~/Documents with a folder icon, ~/.config with a gear, etc.). Inspired by the M365Princess theme.

Custom modules and advanced patterns

The built-in modules cover almost everything, but eventually you'll want something custom — a module that runs your own command and shows the result. That's [custom.NAME], and it's the bridge between Starship and any tool that has a CLI.

A custom module example

toml
# Show the active direnv environment, if any
[custom.direnv]
command = 'echo "$DIRENV_DIR" | sed "s|^.*/||"'
when    = '[ -n "$DIRENV_DIR" ]'
format  = 'env [$output]($style) '
style   = 'bold purple'
shell   = ['bash', '--noprofile', '--norc']
description = 'Show direnv environment if active'

# Show the current 1Password CLI account
[custom.op_account]
command = 'op account list --format=json | jq -r ".[0].shorthand"'
when    = 'command -v op'
format  = '[$output 🔑]($style) '
style   = 'bold yellow'
shell   = ['bash', '--noprofile', '--norc']

# Reference both inside the format string
format = """
$directory $git_branch $git_status \
${custom.direnv}${custom.op_account}\
$character
"""

The four required fields are command (the shell command to run), when (a guard condition — if it exits non-zero, the module skips itself), format (how to render $output), and shell (which shell to invoke). The description field shows up in starship explain and is great documentation for future-you.

Transient prompts

A transient prompt is a different (usually shorter) prompt that replaces the previous one once you press Enter, so your scrollback stays clean. This is supported in zsh, fish, bash, and PowerShell. The pattern looks like this in zsh:

toml
# In starship.toml — nothing special

# In ~/.zshrc, after the starship init line:
# eval "$(starship init zsh --print-full-init)"
# enable transient prompt:
# zle-line-init() { zle reset-prompt; }
# zle -N zle-line-init

The full per-shell setup lives in the advanced config docs — Bash, Fish, and PowerShell each have their own snippet. Once enabled, your scrollback shows just ❯ command for every previous prompt, instead of the full multi-line prompt.

Pre-prompt and pre-execution hooks

Starship lets you hook a function to run before each prompt or before each command. Useful for one-off side effects like a daily message or a notification:

bash
# In ~/.bashrc
function blastoff() { echo "🚀"; }
starship_precmd_user_func="blastoff"

Now 🚀 prints before every prompt. The same hook variable name (starship_precmd_user_func) works in zsh; fish and PowerShell have their own equivalents documented on the advanced config page.

STARSHIP_CONFIG and per-project configs

Set the STARSHIP_CONFIG environment variable to point at any TOML file and Starship will use it instead of the default location:

bash
export STARSHIP_CONFIG=~/.config/starship-work.toml

Combined with direnv, you can have one prompt at home (rainbow, full of icons) and a different one at work (minimal, no AWS module so the customer name doesn't end up in screenshares).

FAQ

Q. Is Starship really faster than powerlevel10k? For the actual prompt render, yes — Starship renders in 1–10 ms for most repos because everything is one Rust binary running modules in parallel. Powerlevel10k uses an "instant prompt" trick to appear fast (it shows a stale prompt while computing the real one), which can feel even faster on the very first render. In day-to-day use, both are fast enough that you won't notice the difference. The win for Starship is that it's the same speed in every shell — bash, fish, PowerShell — not just zsh.

Q. Do I need to uninstall oh-my-zsh? No. Starship overrides only the prompt at the very end of zsh init. Plugins, completion frameworks, and aliases from oh-my-zsh keep working. The only thing replaced is the visual prompt itself.

Q. How do I see what's in the default config? Run starship print-config --default. It dumps the full built-in TOML to stdout — copy the parts you want into your own ~/.config/starship.toml. There's also starship explain which shows which modules are firing for the current directory and how long each took, useful for debugging slow prompts.

Q. My prompt suddenly got slow. How do I find the culprit? starship timings is the answer. Run it inside any directory and it prints the wall-clock time each module took on the last render, sorted slowest first. Custom modules with shell commands are the usual suspects. The other common cause is a python or nodejs module hitting a slow filesystem (NFS, FUSE, encrypted home) — disable the module in that directory with a [disabled] toggle in STARSHIP_CONFIG.

Q. Does Starship work over SSH and inside tmux? Yes. The binary lives on the remote machine, not your local one — install starship on the server, add the eval line to the server's bashrc, and you're done. There's even a built-in hostname module that auto-shows the host only when you're in an SSH session, so your prompt looks normal locally but adds the hostname when you connect remotely.

Q. Can I have a different prompt for different projects? Yes — point STARSHIP_CONFIG at a different TOML file. The cleanest way is via direnv: drop export STARSHIP_CONFIG=$PWD/.starship.toml into a project's .envrc and direnv loads it whenever you cd in. Combine with the dotfiles guide to version-control everything.

Q. Why did my Nerd Font icons disappear after upgrading to macOS 26? The OS shipped with a new default terminal font that doesn't include Nerd Font glyphs. Reinstall a Nerd Font (FiraCode, JetBrainsMono, Hack) and re-select it in Terminal.app or iTerm2 preferences. Starship itself doesn't change between macOS releases — it's always a font-side issue.

Q. Is Starship written in Rust because it has to be? Yes, in practice. A prompt runs on every shell command, so the cost of GC pauses, fork-exec overhead, or interpreter startup adds up. Starship needs to read your .git/HEAD, parse package.json, check the kubernetes context, and render — all in single-digit milliseconds. Rust gives that for free with no runtime dependency, just like the other Rust CLIs in this series. Go would also work, but Rust's zero-cost iterator chains and lack of forced GC pauses make the worst-case latency tighter.

Wrapping Up

Starship is the single-config prompt that survives every shell change and every machine migration. The four things to remember after reading this article:

  1. Install once, configure once. curl -sS https://starship.rs/install.sh | sh, one eval line, one ~/.config/starship.toml.
  2. Start from a preset. Run starship preset tokyo-night -o ~/.config/starship.toml and tweak from there. Don't write the config from scratch unless you enjoy that.
  3. Use palettes for theming. Define colors once in [palettes.NAME], reference them everywhere, switch the active palette with one line.
  4. Run starship timings when the prompt slows down. It points straight at the slow module.

Starship pairs naturally with the rest of the modern CLI series: bat for syntax-highlighted file viewing, delta for diffs, ripgrep for searching, fzf for fuzzy finding, gh CLI for GitHub work, and Starship as the prompt that ties them all together. Check the dotfiles guide for the right way to version-control starship.toml alongside your shell setup, and why Rust CLI tools are so fast for the technical story behind the performance you just inherited.