#14047·ohmyzsh

feat(cli): add `omz generate theme` to scaffold custom themes

Author: robbyrussellCreated Sep 5, 2026Updated Sep 6, 2026
LabelsFeature

The problem

Custom themes are the most fun thing you can do with Oh My Zsh and also the most under-explained. custom/themes/example.zsh-theme is one PROMPT= line. To get anywhere from there you need to know prompt escapes (%~, %(?:a:b)), that $fg[red] has to be wrapped in %{ %} but %F{red} doesn't, that git_prompt_info exists and is styled through ZSH_THEME_GIT_PROMPT_*, and which of the 150+ themes in themes/ are worth cribbing from. All of that is scattered across the wiki, lib/, and other people's themes.

There's a second reason this matters: we don't accept new themes upstream anymore. Custom themes are exactly where we want people making things... and then sharing them in Discord or on social, not in a PR. The tool should say so.

What I'm building: omz generate theme

The second generator, following #14045 / #14046. Same shape, same machinery.

Usage: omz generate theme [<name>] [options]

  -s, --style <style>    Starting point: minimal (default), two-line, or right
      --no-git           Leave git branch/status out of the prompt
      --set              Set it as your theme in .zshrc when done
                         (restarts your shell)
  -y, --yes              Never prompt; use defaults for anything not given

Creates $ZSH_CUSTOM/themes/<name>.zsh-theme. One file: the theme is the guide.

What a session looks like

$ omz generate theme
omz::generate::theme: Theme name: sunrise
omz::generate::theme: Starting point? [1] minimal one-liner  [2] two lines  [3] info on the right  [1]: 1
omz::generate::theme: Show the git branch and status? [Y/n] y
omz::generate::theme: Set sunrise as your theme now? [y/N] n

      create  ~/.oh-my-zsh/custom/themes/sunrise.zsh-theme

omz::generate::theme: theme 'sunrise' generated.

Next steps:

  1. Edit it:      vim ~/.oh-my-zsh/custom/themes/sunrise.zsh-theme
  2. Try it now:   omz theme use sunrise
  3. Keep it:      omz theme set sunrise
  4. Show it off:  https://discord.gg/ohmyzsh  (or tag @ohmyzsh with a screenshot)

omz theme use already exists and previews the theme in the current shell without touching .zshrc, which makes the edit/preview loop instant.

What gets generated

The header comment is the real feature. In order:

  1. How to try it (omz theme use / omz theme set).
  2. What kind of prompt do you want? Three shapes, each with a one-line sketch of what it looks like: minimal one-liner, two-line with the cursor alone on the second line, and short-left/info-right with RPROMPT. The user picks one and deletes the rest.
  3. Building blocks: a prompt-escape cheat sheet (%n %m %~ %c %# %? %T %(?:ok:bad)), colors (%F{red}, %B, %K{blue}, 256-color %F{208}, truecolor %F{#ff8800}, the $fg/$fg_bold/$reset_color forms and why they need %{ %}, the eight named colors), and the *_prompt_info functions with a one-liner to list them all.
  4. References: the wiki Themes and Customization pages, the zsh prompt-expansion docs, and ls $ZSH/themes for ideas.
  5. Show it off: Discord, X, Mastodon, with a note that themes live in your own dotfiles rather than in Oh My Zsh and a link to the External themes wiki page.

Then a small, working prompt (arrow that goes red on failure, cyan directory, git info) with the two-line and RPROMPT variants present but commented out, and the ZSH_THEME_GIT_PROMPT_* settings explained.

Draft: sunrise.zsh-theme (minimal style, with git)
# sunrise theme
#
# A custom Oh My Zsh theme. Try it in this shell without changing anything:
#
#   omz theme use sunrise
#
# and keep it once you like it:
#
#   omz theme set sunrise
#
# ---------------------------------------------------------------------------
# What kind of prompt do you want?
#
# Most prompts are one of three shapes. Pick the one that matches how you
# work and delete the others.
#
#   1. One line, minimal. Fast to read, leaves room for long commands.
#        ➜ ohmyzsh git:(master) ✗
#
#   2. Two lines. Context on the first line, your cursor alone on the second.
#        robby@laptop ~/projects/ohmyzsh git:(master) ✗
#        ❯
#
#   3. Right-side info. Keep the left short and put the clock, exit code or
#      git status on the right with RPROMPT.
#        ➜ ohmyzsh                                          master ✗  14:20
#
# ---------------------------------------------------------------------------
# Building blocks
#
# Prompt escapes (the full list: man zshmisc, "EXPANSION OF PROMPT SEQUENCES")
#   %n  username           %m  hostname          %~  directory (~ for $HOME)
#   %c  current dir name   %2~ last two dirs     %#  # for root, % otherwise
#   %?  last exit code     %T  time (24h)        %D{%a %b %d}  strftime date
#   %(?:ok:bad)  "ok" if the last command succeeded, "bad" if it failed
#
# Colors. Oh My Zsh loads the `colors` module, so these all work:
#   %F{red}text%f          foreground, with %B ... %b for bold
#   %K{blue}text%k         background
#   %F{208}                256 colors: https://www.ditig.com/256-colors-cheat-sheet
#   %F{#ff8800}            24-bit, if your terminal supports it
#   $fg[red] $fg_bold[red] $bg[red] $reset_color
#     The $fg/$bg forms must be wrapped in %{ ... %} so zsh knows they take
#     up no space, e.g. %{$fg[red]%}text%{$reset_color%}
#   Named colors: black red green yellow blue magenta cyan white
#
# Info from plugins. Any *_prompt_info function can go straight in the prompt:
#   $(git_prompt_info)          branch and dirty state, styled by the
#                               ZSH_THEME_GIT_PROMPT_* variables below
#   $(virtualenv_prompt_info)   $(rvm_prompt_info)   $(nvm_prompt_info)
#   Find more with:  print -l ${(k)functions[(I)*_prompt_info]}
#
# References
#   https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
#   https://github.com/ohmyzsh/ohmyzsh/wiki/Customization#overriding-and-adding-themes
#   https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html
#   Themes that ship with Oh My Zsh, for ideas:  ls $ZSH/themes
#
# ---------------------------------------------------------------------------
# Made something you like? Show it off!
#
#   Discord:  https://discord.gg/ohmyzsh  (#themes)
#   X:        https://x.com/ohmyzsh       Mastodon: https://mstdn.social/@ohmyzsh
#
# Tag @ohmyzsh and include a screenshot. Themes live in your own dotfiles or
# repo rather than in Oh My Zsh itself, so this is how other people find them:
# https://github.com/ohmyzsh/ohmyzsh/wiki/External-themes
# ---------------------------------------------------------------------------

# Green arrow when the last command succeeded, red when it failed
PROMPT='%(?:%F{green}➜:%F{red}➜)%f '
# Current directory
PROMPT+='%F{cyan}%c%f '
# Git branch and status (see the settings below)
PROMPT+='$(git_prompt_info)'

# Uncomment for a two-line prompt: everything above on line one, cursor below
# PROMPT+=$'\n'"%F{magenta}❯%f "

# Uncomment for a clock on the right
# RPROMPT='%F{240}%T%f'

# How git_prompt_info looks. Prefix and suffix wrap the branch name; dirty and
# clean are appended depending on whether there are uncommitted changes.
ZSH_THEME_GIT_PROMPT_PREFIX="%F{blue}git:(%F{red}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%f "
ZSH_THEME_GIT_PROMPT_DIRTY="%F{blue}) %F{yellow}✗"
ZSH_THEME_GIT_PROMPT_CLEAN="%F{blue})"

--style two-line uncomments the second-line block; --style right uncomments RPROMPT and moves the git info into it; --no-git drops the git_prompt_info line and the ZSH_THEME_GIT_PROMPT_* block.

Design notes

Reuses the plugin generator's guts. ask, render and created from #14046 move from _omz::generate::plugin::* to _omz::generate::* so both generators share them. Templates go in templates/generators/theme/, next to templates/generators/plugin/, and get the same zsh -n in CI.

Single file, in $ZSH_CUSTOM/themes/. That's the newer of the two custom lookup locations ($ZSH_CUSTOM/<name>.zsh-theme still works for compatibility) and it's what omz theme list scans. No README: built-in themes are flat files and a theme's documentation is its header.

Style variants are one template plus a few toggled lines, not three templates. The two-line and right-side blocks are always present so the user can switch later by uncommenting. The generator just decides which lines start uncommented.

Same name rules as plugins (^[a-z0-9][a-z0-9_-]*$), for the same reason: the name ends up in a path and, via --set, in the ZSH_THEME= rewrite that omz theme set does. Existing custom theme → hard error. Same name as a built-in → warn and confirm, since custom overrides built-in by design.

The generated theme has to actually work, not just parse. The test sources lib/theme-and-appearance.zsh, lib/git.zsh and lib/prompt_info_functions.zsh in a zsh -f, sources the generated file, and runs print -P -- "$PROMPT". That catches a broken escape sequence, which zsh -n never would.

--set reuses _omz::theme::set, which does the .zshrc backup/rewrite/zsh -n/rollback dance and then reloads. Last thing the command does, after the next-steps output.

Out of scope for v1

Powerline/segment-style prompts, async git status, a p10k-style configuration wizard, previewing colors in the terminal, and anything that installs fonts. If people want those they'll find the themes that do them.

Filing this so there's a place to track it. Will start once #14046 lands so the shared helpers have a home.


Co-authored with Claude Code (used for the research and drafting this proposal; the template draft was sourced and its prompt expanded in a zsh -f to check it works)