← Back to DevBytes

Advanced Fish: Plugin Management Techniques

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:

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:

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"

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles