Advanced Fish: Plugin Management Techniques
What is Fish Plugin Management?
Fish shell, known for its user-friendly defaults and powerful scripting capabilities, supports a vibrant ecosystem of plugins. Plugin management in Fish refers to the tools and strategies used to install, update, remove, and configure community-contributed scripts, functions, completions, and prompts. Unlike some other shells that rely on complex framework files, Fish plugins are typically simple collections of .fish files placed in the appropriate directories under ~/.config/fish.
At its core, plugin management in Fish is about organizing external code so it integrates seamlessly with your shell environment. The most popular plugin manager is Fisher, a lightweight, fast manager written entirely in Fish. Others include Oh My Fish and Fundle. However, "advanced" techniques go beyond basic install/uninstall commands—they involve custom repositories, conditional loading, dependency resolution, version pinning, and performance optimization.
Why It Matters
Effective plugin management is crucial for maintaining a clean, fast, and reliable shell environment. Without proper techniques, you may face:
- Slow startup times – Loading too many plugins indiscriminately can degrade shell responsiveness.
- Conflicts – Two plugins might define functions or variables with the same name.
- Dependency hell – A plugin requires another plugin, but you don't know it.
- Version mismatch – An update to a plugin breaks your workflow.
- Difficult reproducibility – Replicating your setup on another machine becomes a chore.
Advanced techniques allow you to fine-tune when and how plugins load, lock versions, create isolated plugin environments, and even write your own plugin repositories. This leads to a faster, more predictable shell experience and makes sharing configurations with teams or across machines straightforward.
Getting Started: Setting Up Fisher
Before diving into advanced methods, ensure you have a plugin manager. Fisher is the recommended choice due to its simplicity and speed. Install it with:
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
Once installed, basic usage includes:
fisher install– install a pluginfisher remove– remove itfisher update– update all pluginsfisher list– show installed plugins
Now let’s move beyond these basics.
Advanced Plugin Management Techniques
1. Custom Plugin Repositories
By default, Fisher installs plugins from GitHub. But you can point it to any Git repository, including private repos or local directories. This is useful for team-specific plugins or for hosting your own curated collection.
To install from a private GitHub repo, use the full URL:
fisher install https://github.com/my-org/my-private-plugin.git
For a local repository (e.g., during development), you can specify a local path:
fisher install /home/user/dev/my-plugin
You can also define a custom registry. Fisher reads the fisher_plugins variable in ~/.config/fish/fish_plugins. If you want to use a different registry, you can create a file that lists plugins and then source it before running fisher update. However, a more elegant approach is to use a fishfile (a plain text list) and a simple function to load them:
# ~/.config/fish/functions/load_plugins.fish
function load_plugins
set -l fishfile "$HOME/.config/fish/my_fishfile"
if test -f $fishfile
while read -la plugin
if not string match -qr '^\s*#' $plugin
fisher install $plugin
end
end < $fishfile
end
end
2. Conditional Loading
Loading every plugin unconditionally can slow down your shell. Advanced users conditionally load plugins only when needed. For example, you might only load a Docker completion plugin when you're working in a project that uses Docker.
One technique is to use --query or check for the presence of a command. Here’s an example that loads a Kubernetes plugin only if kubectl is installed:
# ~/.config/fish/config.fish
if command -sq kubectl
fisher install evanlucas/fish-kubectl-completions
end
Important: Running fisher install inside config.fish will execute every shell startup. That’s inefficient. A better pattern is to use a lazy-loading function that installs the plugin on first use:
# ~/.config/fish/config.fish
function kubectl --wraps kubectl
if not functions -q __kubectl_completions
fisher install evanlucas/fish-kubectl-completions
source (fisher --cache)/evanlucas/fish-kubectl-completions/completions/kubectl.fish
end
command kubectl $argv
end
This installs the plugin only the first time you type kubectl in a session. For even more granularity, you can combine with set -q flags to track whether the plugin has already been loaded.
3. Plugin Dependencies
Some plugins require other plugins to function. Fisher does not natively resolve dependencies, but you can manage them manually using a fish_plugins file that lists dependencies in order. For advanced dependency handling, consider using Fundle which supports a fundle plugin 'user/repo' syntax with dependencies.
If you stick with Fisher, create a helper function that installs a plugin and its dependencies:
function install_with_deps
set -l plugin $argv[1]
switch $plugin
case 'my-org/awesome-prompt'
fisher install jorgebucaran/hydro
fisher install my-org/awesome-prompt
case 'my-org/color-tools'
fisher install my-org/color-base
fisher install my-org/color-tools
end
end
Then run install_with_deps my-org/awesome-prompt to install both the dependency and the main plugin.
4. Version Pinning
To avoid unexpected breakage from plugin updates, pin plugins to specific versions (tags or commits). Fisher allows installing from a specific branch, tag, or commit hash:
fisher install jorgebucaran/hydro@v2.0.0
fisher install my-plugin@abc1234 # commit hash
You can also lock all plugins by storing the exact commit hash in a lockfile. Create a script that generates a lockfile:
# ~/.config/fish/functions/lock_plugins.fish
function lock_plugins
set -l lockfile "$HOME/.config/fish/fish_plugins.lock"