Zsh’s prompt is actually a surprisingly powerful, albeit often misunderstood, control mechanism for your entire shell session.

Let’s see it in action. Imagine you’re working on a project, and you want to know at a glance which Git branch you’re on, the current directory, and if the last command failed.

# Basic prompt with Git branch and directory
PS1="%{$fg[blue]%}%~ %{$reset_color%}%(?.%{$fg[red]%}.%{$fg[green]%})%(#.$%s.#.#)%{$reset_color%} "

# Add a right prompt (RPROMPT) for time
RPROMPT="%{$fg[cyan]%}%T%{$reset_color%}"

# Now, when you're in a Git repo, your prompt looks like this:
# ~/my-project (main) $
# And your right prompt shows the time:
#                                       10:35:15

# If the last command failed (e.g., you type 'ls /nonexistent'), it changes:
# ~/my-project (main) #
#                                       10:36:01

The magic here is in PS1 (the primary prompt string) and RPROMPT (the right-aligned prompt string). These aren’t just static text; they’re miniature programs that Zsh evaluates every time it needs to display a prompt.

The core problem these prompts solve is context. When you’re deep in a terminal session, juggling multiple projects or tasks, a well-configured prompt acts as an instant status dashboard. It tells you where you are (directory), what you’re doing (Git branch, environment), and the state of your last action (success/failure). This reduces cognitive load and prevents mistakes.

Let’s break down how PS1 works:

  • %{$fg[blue]%}: This is Zsh’s way of adding ANSI color codes. fg[blue] sets the foreground color to blue. The %{...%} tells Zsh that this sequence doesn’t consume a character on the line, which is crucial for correct cursor positioning.
  • %~: This is a Zsh prompt escape sequence that expands to the current working directory, using ~ for your home directory.
  • %{$reset_color%}: Resets the color back to the default.
  • %(?. ... . ...): This is a conditional expression. ? checks the exit status of the last command. If it was successful (0), the part after the first . is displayed. If it failed (non-zero), the part after the second . is displayed.
  • .%{$fg[red]%}.%{$fg[green]%}: Inside the conditional, %{$fg[red]%} makes the prompt red if the last command failed, and %{$fg[green]%} makes it green if it succeeded.
  • %(#.$%s.#.#): Another conditional. # checks if the current user is root. If it is, it displays $. Otherwise, it displays #. %s is the literal character following the conditional.
  • : A literal space at the end, separating the prompt from your command input.

RPROMPT is simpler:

  • %{$fg[cyan]%}: Sets foreground color to cyan.
  • %T: This is a Zsh escape sequence for the current time in 12-hour HH:MM:SS format.
  • %{$reset_color%}: Resets color.

The true power comes from combining these built-in sequences with custom functions and external command outputs. You can, for instance, fetch battery status, network information, or even the current weather.

The most powerful aspect, and the one most people overlook, is how Zsh’s prompt expansion happens within a subshell context for certain escape sequences. This means you can execute complex commands or even external scripts directly within your PS1 or RPROMPT string, and Zsh will capture their output without affecting your current shell’s state. For example, %(?.$(command_that_might_fail).error_message) will execute command_that_might_fail, and if it returns a non-zero exit code, error_message is displayed. The internal workings of command_that_might_fail are isolated from your main shell.

Once you’ve mastered PS1 and RPROMPT, you’ll likely want to explore prompt themes and frameworks like Oh My Zsh or Prezto, which provide pre-built, highly customizable prompt experiences.

Want structured learning?

Take the full Zsh course →