How Lazygit's Custom Command System Works: Complete Guide to Parameters and Templates
Lazygit's custom command system allows users to define shell commands in ~/.config/lazygit/config.yml that become interactive keybindings supporting dynamic prompts, Go templating, and flexible output routing.
The lazygit custom command system transforms static YAML declarations into powerful, context-aware Git workflows. By defining commands in your configuration file, you can extend the terminal UI with personalized automation while leveraging lazygit's internal state and UI components.
Configuration Structure and YAML Fields
Custom commands are defined in the customCommands array within your lazygit config. Each command supports fields that control its triggering, interaction model, and execution environment.
Core Command Fields
- key: Single-letter or special keybinding that triggers the command (e.g.,
"b","<c-r>") - context: Comma-separated UI contexts where the binding is active (e.g.,
status,files,global) - command: Shell command template using Go syntax (e.g.,
git checkout -b {{.Form.Branch}}) - description: Human-readable label displayed in the keybinding menu
- loadingText: Status message shown during execution
- output: Destination for command output (
none,terminal,log,logWithPty,popup) - outputTitle: Popup title template when
output: popup - prompts: Ordered array of interactive prompts to display before execution
- after: Post-execution hooks (currently supports
checkForConflicts: true) - commandMenu: Array of subcommands for creating nested menus (mutually exclusive with other fields)
Source: [pkg/config/user_config.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/user_config.go#L53-L78)
Prompt Configuration Fields
Each prompt in the prompts array supports:
- type: Interaction style (
input,menu,menuFromCommand,confirm) - key: Symbolic identifier for template access (e.g.,
{{.Form.MyKey}}) - title: Popup window title
- initialValue: Default text for input prompts
- suggestions: Auto-completion source using
preset(authors,branches,files,refs,remotes,remoteBranches,tags) or customcommand - body: Confirmation message for
confirmtype - options: Static array of choices for
menutype (each withname,description,value,key) - command: Shell command generating menu items for
menuFromCommand - filter: Regex with named groups parsing command output
- valueFormat: Go template defining the stored value
- labelFormat: Optional display template (defaults to
valueFormat)
Source: [pkg/config/user_config.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/user_config.go#L86-L122)
The Execution Pipeline
When you trigger a custom command, lazygit processes it through a six-stage pipeline implemented across multiple packages.
Configuration Parsing
Lazygit unmarshals UserConfig.CustomCommands from YAML into the config.CustomCommand struct during startup. This validates syntax and prepares the command definitions for runtime binding.
Source: [pkg/config/user_config.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/user_config.go#L53-L78)
Handler Creation and Keybindings
For each custom command, custom_commands.NewHandlerCreator generates a handler function stored in the keybinding map. The keybindings/custom_command_keybindings.go file registers these handlers according to their context field, determining which UI panels activate the command.
Source: [pkg/gui/services/custom_commands/handler_creator.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/handler_creator.go#L29-L48)
Interactive Prompt Stack Building
If your command defines prompts, HandlerCreator.call constructs a nested function stack. Prompts process inside-out—the first prompt defined appears outermost in the UI. Each prompt captures user input into a form map that persists through subsequent prompts and the final command.
The system supports four prompt types:
- inputPrompt: Free text entry with optional suggestions
- menuPrompt: Selection from static options
- menuPromptFromCommand: Dynamic menu populated by shell command output
- confirmPrompt: Binary yes/no confirmation
Source: [pkg/gui/services/custom_commands/handler_creator.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/handler_creator.go#L56-L100)
Template Resolution
Lazygit uses Go's text/template engine with a custom FuncMap providing:
- quote: OS-specific string quoting via
c.OS().Quote - runCommand: Executes a command and returns its single-line output (implemented in
git_commands.Custom.TemplateFunctionRunCommand)
The utils.ResolveTemplate helper expands placeholders like {{.Form.Branch}} or {{.SelectedBranch.Name}} using data from session_state_loader.go, which captures the current application state (selected files, branches, commits).
Source: [pkg/gui/services/custom_commands/handler_creator.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/handler_creator.go#L45-L57) and [pkg/commands/git_commands/custom.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/git_commands/custom.go#L27-L40)
Shell Execution and Output Routing
After template resolution, the final string passes to c.OS().Cmd.NewShell. The output field determines execution behavior:
- none: Execute silently, discard output
- terminal: Run in a subprocess that takes over the UI (
c.RunSubprocessAndRefresh), blocking until completion - log: Stream stdout/stderr to the command log panel
- logWithPty: Stream with a pseudo-terminal to preserve ANSI colors
- popup: Display output in a modal panel using
outputTitleas the header
Source: [pkg/gui/services/custom_commands/handler_creator.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/handler_creator.go#L67-L88)
Post-Execution Hooks
If the command includes an after block with checkForConflicts: true, any shell error routes through mergeAndRebaseHelper.CheckForConflicts. This surfaces Git merge/rebase conflicts in lazygit's standard conflict resolution UI.
Source: [pkg/gui/services/custom_commands/handler_creator.go](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/handler_creator.go#L91-L97)
Practical Configuration Examples
Creating a Branch with Tab Completion
customCommands:
- key: "b"
description: "Create a new branch"
command: "git checkout -b {{.Form.Branch}}"
context: "global"
output: "log"
prompts:
- type: "input"
key: "Branch"
title: "Branch name"
suggestions:
preset: "branches"
This binds b globally to branch creation. The input prompt uses the branches preset for tab-completion against existing branch names. The value is stored in {{.Form.Branch}} and injected into the checkout command.
Dynamic Menu from Remote Branches
customCommands:
- key: "d"
description: "Delete remote branch"
command: "git push {{.Form.Remote}} --delete {{.Form.Branch}}"
context: "global"
output: "popup"
prompts:
- type: "menuFromCommand"
key: "Remote"
title: "Select remote"
command: "git remote"
- type: "menuFromCommand"
key: "Branch"
title: "Select remote branch"
command: "git ls-remote --heads {{.Form.Remote}}"
filter: "^.+\\trefs/heads/(?P<branch>.+)$"
valueFormat: "{{ .branch }}"
labelFormat: "{{ .branch | green }}"
The first prompt lists remotes via git remote. The second executes git ls-remote against the selected remote, parsing output with the filter regex. Named capture groups populate the template context for valueFormat and labelFormat, with the latter piping through a color function.
Embedding Command Output with runCommand
customCommands:
- key: "m"
description: "Show current branch"
command: "echo Current branch is {{ runCommand \"git rev-parse --abbrev-ref HEAD\" }}"
context: "status"
output: "popup"
The runCommand template function executes the inner Git command, captures its single-line output, and injects it into the outer command string before shell execution.
Conflict Detection After Merge
customCommands:
- key: "m"
description: "Merge branch"
command: "git merge {{.Form.Branch}}"
context: "branches"
output: "log"
after:
checkForConflicts: true
prompts:
- type: "menu"
key: "Branch"
title: "Select branch to merge"
options:
- name: "main"
description: "Main branch"
value: "main"
- name: "develop"
description: "Develop branch"
value: "develop"
The after hook monitors the merge command. If Git returns a conflict error, lazygit automatically invokes its conflict detection logic, highlighting conflicted files in the UI without manual refresh.
Summary
- Lazygit custom commands are defined in
config.ymland bound to keys within specific UI contexts. - The system supports four prompt types (input, menu, menuFromCommand, confirm) that build a nested execution stack.
- Template functions include
quotefor OS-safe strings andrunCommandfor composable command pipelines. - Output routing offers five destinations: silent (
none), blocking terminal (terminal), log panel (log), color-preserving log (logWithPty), or modal popup (popup). - State injection provides access to selected branches, files, and commits via templates like
{{.SelectedBranch.Name}}. - Post-execution hooks can trigger conflict detection automatically after commands that might create merge conflicts.
Frequently Asked Questions
How do I access the currently selected branch in a custom command template?
Use the {{.SelectedBranch.Name}} template variable. The session_state_loader.go file captures the active UI state and injects it into the template context alongside {{.Form}} values from prompts. Other available state includes {{.SelectedFile.Name}}, {{.SelectedCommit.Sha}}, and {{.SelectedRemote.Name}}.
What is the difference between menu and menuFromCommand prompts?
menu prompts display a static list defined in the options array of your YAML configuration. menuFromCommand prompts execute a shell command dynamically, parse the output using the filter regex, and generate menu items from the results. The latter is ideal for lists that change frequently, such as remote branches or tag names.
Can I run interactive terminal applications like vim or nano from a custom command?
Yes, set output: terminal in your command configuration. This invokes c.RunSubprocessAndRefresh, which suspends lazygit's UI and hands control to the subprocess until it exits. This is necessary for any command requiring full terminal control, including interactive editors, pagers, or credential helpers.
Why does my runCommand template function return empty output?
The runCommand function returns only the first line of command output and strips trailing newlines. If your command produces no output or multiple lines, only the initial line is captured. For multi-line processing, use menuFromCommand with appropriate filter and valueFormat templates instead.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →