Understanding the Lazygit Keybinding System: How to Create Custom Keybindings

The lazygit keybinding system translates human-readable labels like <c-p> into low-level key codes using the GetKey function, enabling users to define custom shortcuts by adding customCommands entries to config.yml or creating Binding structs programmatically.

The lazygit keybinding system provides a flexible, type-safe architecture built on the gocui UI library that separates declarative configuration from runtime execution. By mapping human-readable labels to internal key representations, the system allows you to bind any command—built-in or user-defined—to specific keys within particular views or globally across the application.

How the Lazygit Keybinding System Works

At its core, the lazygit keybinding system bridges human-readable configuration and the low-level key codes used by the underlying gocui UI library. The architecture centers on the Binding struct, which encapsulates everything needed to associate a key press with an action.

Core Types and Structures

The foundation of the system resides in pkg/gui/types/keybindings.go, which defines the Binding struct that represents a single keybinding:

type Binding struct {
    ViewName          string                // view that must have focus; empty = global
    Handler           func() error          // command executed on key press
    Key               Key                   // rune or gocui.Key
    Modifier          gocui.Modifier        // e.g. ModAlt, ModCtrl (rarely used)
    Description       string                // shown in the cheatsheet
    // … additional UI fields (short description, tags, display style, tooltip, etc.)
}

The Key type is defined as type Key any and can hold either a rune for character keys or a gocui.Key for special keys like function keys or arrow keys. When you define a custom keybinding in your configuration, the KeybindingCreator in pkg/gui/services/custom_commands/keybinding_creator.go constructs one of these Binding structs by resolving your string label into the appropriate Key value.

Label-to-Key Translation

The translation between configuration labels and runtime keys happens in pkg/gui/keybindings/keybindings.go. The GetKey function converts string labels like <c-p> or <f5> into the internal Key type used by gocui:

  • If the label is longer than one rune (e.g., <c-p>), the function looks it up in config.KeyByLabel from pkg/config/keynames.go.
  • If it is a single rune, it returns the rune directly.
  • The special label <disabled> yields nil, effectively disabling the binding.

For reverse lookup (used in UI rendering such as the cheatsheet), the LabelFromKey function consults config.LabelByKey to convert internal key representations back to human-readable strings.

Creating Custom Keybindings in Lazygit

You can create custom keybindings either through declarative configuration in config.yml or programmatically if you are extending lazygit's source code.

Configuring Custom Commands via config.yml

The simplest way to add custom keybindings is to edit your ~/.config/lazygit/config.yml file. The customCommands array allows you to bind shell commands to specific keys within defined contexts:

customCommands:
  # Example: Pull the current branch with Ctrl-P in the Files view

  - key: "<c-p>"
    context: "files"
    command: "git pull"
    description: "Pull latest changes"

When lazygit loads, it parses this configuration into a config.CustomCommand struct. The KeybindingCreator then validates the context, calls keybindings.GetKey(customCommand.Key) to resolve <c-p> into an actual Key value, and produces a *types.Binding object linked to the command's handler. Restart lazygit or press Ctrl+R to reload the configuration and activate the new binding.

Disabling Existing Bindings

To disable a keybinding without removing the configuration entry, set the key to <disabled>:

- key: "<disabled>"
  context: "files"
  command: "git pull"
  description: "Pull latest changes (disabled)"

The GetKey function returns nil for this label, causing the binding to be ignored while preserving the entry for future re-enablement.

Programmatic Keybindings for Developers

If you are contributing to lazygit or building a fork, you can create bindings directly in Go. Import the necessary packages and construct a Binding struct:

import (
    "github.com/jesseduffield/gocui"
    "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
    "github.com/jesseduffield/lazygit/pkg/gui/types"
)

func newMyBinding() *types.Binding {
    return &types.Binding{
        ViewName:    "status",               // empty string = global
        Key:         keybindings.GetKey("<c-m>"), // Resolve label → Key
        Modifier:    gocui.ModNone,
        Handler: func() error {
            // Your command logic here
            return nil
        },
        Description: "My custom action",
    }
}

Register this binding with the UI's Gui object by appending it to the keybindings collection, as implemented in the registration loop within pkg/gui/keybindings/keybindings.go.

Practical Examples for Common Use Cases

Global Shortcut to Open Help

To create a binding that works across all views, set the context to global (which maps to an empty ViewName internally):

customCommands:
  - key: "<f1>"
    context: "global"
    command: "help"
    description: "Open the Help panel"

The <f1> label resolves to gocui.KeyF1 via the LabelByKey map in pkg/config/keynames.go.

Context-Specific Script Execution

Bind a custom script to run only when the Commits view has focus:

customCommands:
  - key: "<c-e>"
    context: "commits"
    command: "./scripts/checkout-branch.sh"
    description: "Checkout selected commit's branch"

The KeybindingCreator maps the commits context string to the appropriate view name, ensuring the handler only executes when that panel is active.

Programmatic Toggle Binding

For developers extending the application directly:

func (gui *Gui) addToggleStashBinding() {
    binding := &types.Binding{
        ViewName:    "status",
        Key:         keybindings.GetKey("<c-b>"),
        Modifier:    gocui.ModNone,
        Handler:     gui.toggleStash,
        Description: "Toggle stash view",
    }
    gui.State.Keybindings = append(gui.State.Keybindings, binding)
}

Summary

  • The lazygit keybinding system uses the Binding struct in pkg/gui/types/keybindings.go to associate keys with handlers and view contexts.
  • Label translation occurs through GetKey and LabelFromKey in pkg/gui/keybindings/keybindings.go, using lookup tables defined in pkg/config/keynames.go.
  • Custom commands are created by adding entries to customCommands in config.yml, which the KeybindingCreator processes into runtime bindings.
  • Global bindings use an empty ViewName or the global context, while specific bindings target individual views like files or commits.
  • Disabling bindings requires setting the key to <disabled>, which causes GetKey to return nil.

Frequently Asked Questions

How do I find the correct context name for a specific view?

The context names used in config.yml correspond to the view names defined in lazygit's source code. Common values include files, commits, branches, status, and global. Refer to the KeybindingCreator implementation in pkg/gui/services/custom_commands/keybinding_creator.go or the documentation in docs/keybindings/Custom_Keybindings.md for the complete list of valid context strings.

What is the difference between global and view-specific keybindings?

Global keybindings have an empty ViewName field (specified as context: global in config) and are active regardless of which panel has focus. View-specific bindings only trigger when their designated view is active, allowing you to reuse the same key for different actions in different contexts. The Binding struct's ViewName field controls this behavior.

How do I disable a default lazygit keybinding without modifying the source code?

While you cannot directly override built-in bindings through customCommands, you can disable custom command bindings by setting key: "<disabled>" in your configuration. This causes the GetKey function to return nil, preventing the binding from registering. For built-in bindings, you would need to fork the repository and modify the registration logic in pkg/gui/keybindings/keybindings.go.

Can I bind multiple keys to the same custom command?

Yes, you can define multiple entries in the customCommands array that reference the same command but specify different keys and potentially different contexts. Each entry creates a separate Binding struct through the KeybindingCreator, allowing you to trigger the same shell command or script from different views using different key combinations.

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 →