# How Lazygit's Custom Command System Works: Complete Guide to Parameters and Templates

> Learn how Lazygit's custom command system allows shell commands as keybindings with dynamic prompts Go templating and flexible output routing Explore parameters and templates now

- Repository: [Jesse Duffield/lazygit](https://github.com/jesseduffield/lazygit)
- Tags: deep-dive
- Published: 2026-03-02

---

**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/main/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/main/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/main/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`](https://github.com/jesseduffield/lazygit/blob/main/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/main/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/main/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`](https://github.com/jesseduffield/lazygit/blob/main/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/main/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/main/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/main/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/main/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

```yaml
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

```yaml
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

```yaml
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

```yaml
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`](https://github.com/jesseduffield/lazygit/blob/main/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`](https://github.com/jesseduffield/lazygit/blob/main/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.