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 custom command
  • body: Confirmation message for confirm type
  • options: Static array of choices for menu type (each with name, 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:

  1. inputPrompt: Free text entry with optional suggestions
  2. menuPrompt: Selection from static options
  3. menuPromptFromCommand: Dynamic menu populated by shell command output
  4. 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 outputTitle as 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.yml and 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 quote for OS-safe strings and runCommand for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →