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 inconfig.KeyByLabelfrompkg/config/keynames.go. - If it is a single rune, it returns the rune directly.
- The special label
<disabled>yieldsnil, 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
Bindingstruct inpkg/gui/types/keybindings.goto associate keys with handlers and view contexts. - Label translation occurs through
GetKeyandLabelFromKeyinpkg/gui/keybindings/keybindings.go, using lookup tables defined inpkg/config/keynames.go. - Custom commands are created by adding entries to
customCommandsinconfig.yml, which theKeybindingCreatorprocesses into runtime bindings. - Global bindings use an empty
ViewNameor theglobalcontext, while specific bindings target individual views likefilesorcommits. - Disabling bindings requires setting the key to
<disabled>, which causesGetKeyto returnnil.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →