Understanding Fish Prompt Customization
Fish shell (Friendly Interactive Shell) provides one of the most elegant and powerful prompt customization systems among modern shells. Unlike Bash or Zsh, which rely on cryptic escape sequences and complex substitution syntax, Fish uses a clean, function-based approach. Your entire prompt is defined by a function named fish_prompt that you write once and Fish calls automatically before displaying each prompt line.
What Is the Fish Prompt System?
The Fish prompt system revolves around three special functions that you define in your Fish configuration:
- fish_prompt — Defines the main left-side prompt that appears before your cursor on every line
- fish_right_prompt — Defines an optional right-aligned prompt that appears on the same line
- fish_mode_prompt — Controls the vi-mode indicator displayed when using Fish's vi key bindings
These functions are automatically sourced from your ~/.config/fish/functions/ directory or defined directly in ~/.config/fish/config.fish. Fish calls them every time it needs to render the prompt, which means your prompt can dynamically reflect the current state of your shell, Git repository, environment variables, or any other information you can access in a Fish function.
Why Prompt Customization Matters
A well-designed prompt transforms your terminal from a bland input line into an informative dashboard. Here's what a thoughtfully customized Fish prompt gives you:
- Context at a glance — Current directory, Git branch, and exit status of the last command without typing extra commands
- Reduced cognitive load — Color-coded indicators tell you immediately whether a command succeeded or failed
- Faster workflows — Git status in the prompt eliminates the need to run
git statusbefore every commit - Personal comfort — A prompt that matches your aesthetic preferences makes long coding sessions more pleasant
- Vi mode awareness — Clear indicators prevent the frustrating experience of typing in the wrong vi mode
Core Building Blocks
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →The fish_prompt Function
Every Fish prompt starts with this fundamental function. Fish looks for fish_prompt in your function path and executes it to generate the prompt string. The function must output (via echo) the text that will appear as your prompt. Here's the minimal valid prompt:
function fish_prompt
echo "> "
end
This produces a simple > prompt. But the real power comes from combining Fish's built-in commands for color, path manipulation, and status checking.
Working with set_color
The set_color command is your primary tool for styling. It accepts color names, hex codes, and special modifiers:
# Named colors
set_color red
set_color cyan
set_color yellow
# Hex colors (must use 6-digit hex; 3-digit shorthand is not supported)
set_color ff6b6b
set_color 2ecc71
# Special options
set_color normal # Reset to default terminal text color
set_color --bold # Bold text
set_color --underline # Underlined text
set_color --italics # Italic text (if terminal supports it)
set_color -b cyan # Set background color
set_color --dim # Dimmed/bright text
# Reset everything
set_color --bold red --background blue
# Always reset with:
set_color normal
Critically, set_color affects all subsequent output until you call set_color normal. Always reset colors at the end of your prompt function to avoid color bleeding into the user's typed commands.
Accessing Essential Prompt Information
Fish provides several variables and commands that are indispensable for prompt construction:
# Current working directory (shortened)
echo $PWD
# Custom path shortening with prompt_pwd
prompt_pwd # Shortened path like ~/p/s/fish
prompt_pwd --full-length # Full path
prompt_pwd --dir-length 1 # Show only first component of each directory
# Last command exit status
echo $status # 0 for success, non-zero for failure
# Current user and host
echo $USER
echo $hostname # or prompt_hostname
# Current time
date "+%H:%M:%S"
Building Your First Custom Prompt
A Simple Two-Segment Prompt
Let's build a prompt that shows the current directory and a prompt symbol that changes color based on the last command's success or failure:
function fish_prompt
# Save the exit status immediately
set -l last_status $status
# Start with the current directory in blue
set_color blue
echo -n (prompt_pwd)
# Add a space
echo -n " "
# Show prompt symbol: green arrow for success, red for failure
if test $last_status -eq 0
set_color green
echo -n "❯"
else
set_color red
echo -n "❯"
end
# Add a trailing space and reset color
echo -n " "
set_color normal
end
This produces a prompt like ~/Documents/project ❯ when the last command succeeded, or ❯ when it failed. Notice the critical pattern: always capture $status at the very top of your function. Any command you run inside fish_prompt (including echo, set_color, or test) will overwrite $status, so saving it first prevents losing the original exit code.
Adding User and Host Information
For SSH sessions or multi-user systems, showing user and host adds valuable context:
function fish_prompt
set -l last_status $status
# Show user@host in dim style
set_color --dim cyan
echo -n $USER@(prompt_hostname)
echo -n " "
# Directory in bright blue
set_color blue --bold
echo -n (prompt_pwd)
# Reset bold but keep color context for symbol
set_color normal
echo -n " "
# Prompt symbol
if test $last_status -eq 0
set_color green
echo -n "❯"
else
set_color red
echo -n "❯"
end
echo -n " "
set_color normal
end
Integrating Git Information
Manual Git Status Detection
Fish can check if you're in a Git repository and display branch information using simple command substitution:
function fish_prompt
set -l last_status $status
# Check if we're in a git repository
set -l git_branch (git rev-parse --abbrev-ref HEAD 2>/dev/null)
if test -n "$git_branch"
# We're in a git repo - show branch name in magenta
set_color magenta
echo -n " $git_branch"
# Check for dirty state (unstaged changes)
set -l git_dirty (git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
if test "$git_dirty" -gt 0
echo -n " ●" # Dirty indicator
else
echo -n " ○" # Clean indicator
end
echo -n " "
end
# Directory path
set_color blue
echo -n (prompt_pwd)
echo -n " "
# Prompt symbol
if test $last_status -eq 0
set_color green
echo -n "❯"
else
set_color red
echo -n "❯"
end
echo -n " "
set_color normal
end
More Detailed Git Status
For a richer Git display, you can check specific states like staged changes, unstaged changes, and untracked files:
function fish_prompt
set -l last_status $status
# Git branch detection
set -l git_branch (git rev-parse --abbrev-ref HEAD 2>/dev/null)
set -l git_dir (git rev-parse --git-dir 2>/dev/null)
if test -n "$git_dir"
set_color magenta --bold
echo -n " $git_branch"
# Detailed status indicators
set -l git_staged (git diff --staged --name-only 2>/dev/null | wc -l | string trim)
set -l git_unstaged (git diff --name-only 2>/dev/null | wc -l | string trim)
set -l git_untracked (git ls-files --others --exclude-standard 2>/dev/null | wc -l | string trim)
if test "$git_staged" -gt 0
set_color green
echo -n " +$git_staged"
end
if test "$git_unstaged" -gt 0
set_color yellow
echo -n " ~$git_unstaged"
end
if test "$git_untracked" -gt 0
set_color red
echo -n " ?$git_untracked"
end
set_color normal
echo -n " "
end
set_color blue
echo -n (prompt_pwd)
echo -n " "
if test $last_status -eq 0
set_color green
echo -n "❯"
else
set_color red
echo -n "❯"
end
echo -n " "
set_color normal
end
The Right Prompt: fish_right_prompt
The right prompt appears on the same line as your main prompt but aligned to the right edge of the terminal. It's perfect for secondary information like the current time, Python virtual environment, or Node.js version. Fish automatically handles the alignment — your function simply outputs the content:
function fish_right_prompt
# Show current time in dim style
set_color --dim brblack
echo -n (date "+%H:%M:%S")
# Show Python virtual environment if active
if set -q VIRTUAL_ENV
set_color cyan
echo -n " 🐍 "(basename "$VIRTUAL_ENV")
end
set_color normal
end
The right prompt gracefully disappears if your command line extends far enough to collide with it. Fish handles this collision automatically by hiding the right prompt when the cursor approaches it.
Advanced Right Prompt with Duration Display
One popular pattern is showing how long the last command took to execute. Fish provides $CMD_DURATION — the runtime of the previous command in milliseconds:
function fish_right_prompt
set_color --dim brblack
# Show last command duration if it took more than 3 seconds
if test -n "$CMD_DURATION" -a "$CMD_DURATION" -gt 3000
set -l duration_seconds (math -s0 "$CMD_DURATION / 1000")
echo -n "⏱ {$duration_seconds}s "
end
# Show current time
echo -n (date "+%H:%M")
# Show error count if last command failed
if test $status -ne 0
set_color red
echo -n " ✗"
end
set_color normal
end
Vi Mode Prompt Customization
When Fish's vi key bindings are active (enabled with fish_vi_key_bindings), the fish_mode_prompt function controls the mode indicator. This function is called only in vi mode to show whether you're in insert, normal, visual, or replace mode:
function fish_mode_prompt
# Only show mode indicator in non-insert mode to minimize noise
switch $fish_bind_mode
case default
# Insert mode - no indicator (clean prompt)
echo -n ""
case insert
echo -n ""
case replace_one
set_color green --bold
echo -n " [REPLACE] "
set_color normal
case visual
set_color magenta --bold
echo -n " [VISUAL] "
set_color normal
case replace
set_color yellow --bold
echo -n " [REPLACE] "
set_color normal
end
end
The $fish_bind_mode variable contains the current vi mode. The mode prompt is typically displayed at the beginning of the prompt line, before the output of fish_prompt, making it a natural prefix.
Complete Production-Grade Prompt Example
Here's a full-featured prompt combining all the techniques discussed, suitable for daily development use. Save this as ~/.config/fish/functions/fish_prompt.fish:
function fish_prompt
# ─── Capture exit status immediately ───
set -l last_status $status
# ─── Vi mode indicator (only in non-insert modes) ───
if test "$fish_key_bindings" = fish_vi_key_bindings
switch $fish_bind_mode
case replace_one replace
set_color green --bold
echo -n "[R] "
case visual
set_color magenta --bold
echo -n "[V] "
end
set_color normal
end
# ─── Git section ───
set -l git_branch (git rev-parse --abbrev-ref HEAD 2>/dev/null)
set -l git_dir (git rev-parse --git-dir 2>/dev/null)
if test -n "$git_dir"
set_color magenta --bold
echo -n " $git_branch"
# Check for dirty state efficiently
set -l git_dirty (git status --porcelain --ignore-submodules 2>/dev/null | string collect)
if test -n "$git_dirty"
set_color yellow
echo -n " ●"
else
set_color green
echo -n " ○"
end
# Check for stash
set -l git_stash (git stash list 2>/dev/null | count)
if test "$git_stash" -gt 0
set_color cyan
echo -n " ⚑"
end
set_color normal
echo -n " "
end
# ─── Directory section ───
set_color blue --bold
echo -n (prompt_pwd)
# ─── Prompt symbol ───
echo -n " "
if test $last_status -eq 0
set_color green --bold
echo -n "❯"
else
set_color red --bold
echo -n "❯"
# Optionally show the exit code for non-zero status
set_color yellow
echo -n " [$last_status]"
end
echo -n " "
set_color normal
end
function fish_right_prompt
set_color --dim brblack
# ─── Command duration ───
if test -n "$CMD_DURATION" -a "$CMD_DURATION" -gt 2000
set -l secs (math -s0 "$CMD_DURATION / 1000")
echo -n "⏱ {$secs}s "
end
# ─── Python virtual environment ───
if set -q VIRTUAL_ENV
set_color cyan --dim
echo -n "🐍 "(basename "$VIRTUAL_ENV")" "
end
# ─── Node version if in a node project ───
if test -f package.json
set -l node_ver (node -v 2>/dev/null)
if test -n "$node_ver"
set_color green --dim
echo -n "⬢ $node_ver "
end
end
# ─── Time ───
echo -n (date "+%H:%M")
set_color normal
end
Performance Considerations
Why Prompt Speed Matters
Your prompt function runs every time you press Enter. A slow prompt creates a perceptible lag between commands that compounds throughout your workday. Fish prompts should target sub-50ms execution time. The most common performance pitfalls are:
- Uncached Git operations —
git statuson large repositories can take seconds - Network-dependent commands — Anything that touches the network (like checking remote Git status) introduces unpredictable latency
- Python or Node version checks — Spawning external processes for version strings adds overhead
- Complex arithmetic in hot paths — Keep math operations minimal
Optimizing Git Checks
Instead of running full git status, use targeted Git commands that return faster:
# Fast: Check only if branch exists
set -l git_branch (git branch --show-current 2>/dev/null)
# Faster than git status for dirty check on large repos:
set -l git_dirty (git diff --quiet 2>/dev/null; echo $status)
# Avoid: This scans the entire working tree
# git status --porcelain # Slow on large repos!
Caching with Asynchronous Prompts
For complex Git displays, consider moving Git checks to a background job that updates a cached variable. Fish supports this pattern through its event system:
# Define a variable to cache git info
set -g __fish_git_info ""
function __fish_update_git_info --on-event fish_prompt
# Run git checks in a subshell spawned asynchronously
fish -c "
set -l branch (git branch --show-current 2>/dev/null)
if test -n \"\$branch\"
set -l dirty (git diff --quiet 2>/dev/null; or echo 'dirty')
echo \"\$branch|\$dirty\" > /tmp/fish_git_cache.\$fish_pid
end
" &
end
function fish_prompt
# Read from cache
set -l git_cache_file /tmp/fish_git_cache.(string replace -a '%' '' $fish_pid)
if test -f "$git_cache_file"
set -l git_data (cat "$git_cache_file")
# Parse and display...
end
# ... rest of prompt
end
Best Practices Summary
- Always capture
$statusfirst — Store it in a local variable at the top offish_promptbefore any other command modifies it - Always reset colors with
set_color normal— At the end of every prompt function to prevent color leakage into command output - Use local variables — Declare variables with
set -linside prompt functions to avoid polluting the global namespace - Keep Git checks fast — Use
git diff --quietinstead ofgit status --porcelainfor dirty checks; avoid remote operations entirely - Separate concerns — Put primary context in
fish_promptand secondary info (time, versions, duration) infish_right_prompt - Test your prompt in different directories — Ensure it works gracefully outside Git repos, in deeply nested paths, and with special characters in directory names
- Use
prompt_pwdfor paths — It intelligently abbreviates the home directory to~and shortens long paths - Provide visual distinction for errors — A different color or symbol for non-zero exit status gives immediate feedback without requiring conscious attention
- Handle missing commands gracefully — Wrap external command calls in
2>/dev/nullto suppress errors when tools likegitornodearen't available - Document your prompt design — Add comments explaining each section so you can modify it months later without reverse-engineering your own logic
Troubleshooting Common Issues
Prompt Colors Bleeding Into Commands
If your typed commands appear in the wrong color, you've forgotten set_color normal at the end of your prompt function. Add it as the very last command before the function returns.
Prompt Not Updating
If changes to fish_prompt don't take effect, ensure:
- The function file is in
~/.config/fish/functions/with a.fishextension - You've sourced it with
source ~/.config/fish/functions/fish_prompt.fishor restarted Fish - No other function with the same name exists earlier in
$fish_function_path
Git Info Shows for Non-Git Directories
Always check both git_branch and git_dir before displaying Git information. A bare git rev-parse --abbrev-ref HEAD can return "HEAD" even outside a repository in some edge cases. Testing git_dir (which returns the .git directory path) is more reliable.
Right Prompt Disappears
This is expected behavior — Fish hides the right prompt when your command line approaches it to prevent overlap. If you want the right prompt always visible, keep it short (under 30 characters) or use a wider terminal.
Going Further: Conditional Prompts
You can create different prompts for different contexts by checking environment variables or directory characteristics:
function fish_prompt
set -l last_status $status
# Production server warning
if test -f /etc/production-server
set_color red --bold
echo -n "⚠ PRODUCTION "
set_color normal
end
# Different prompts for specific projects
switch (basename $PWD)
case my-web-app
set_color cyan
echo -n "🌐 web-app "
case api-server
set_color magenta
echo -n "🔌 api "
end
# ... rest of prompt
end
Conclusion
Fish prompt customization represents one of the shell's most elegant design decisions. By reducing prompt configuration to a simple function with clear, composable building blocks like set_color, prompt_pwd, and $status, Fish makes prompt design accessible while remaining powerful enough for complex setups. The function-based approach means your prompt is just Fish code — you can use conditionals, loops, external commands, and any logic you need. Start with a simple working prompt, add Git information, then layer on right-prompt details like time and command duration. Keep performance in mind by using fast Git checks and avoiding network calls. With the techniques covered here, you can craft a prompt that serves as a genuine productivity tool rather than just decoration. Your prompt becomes a heads-up display for your terminal workflow, giving you exactly the information you need at exactly the moment you need it.