Helm plugins are essentially Go binaries that live in a specific directory and are invoked by the helm command.

Let’s see a plugin in action. Imagine you want a command to quickly list all deployed Helm releases across all namespaces, along with their status and chart name.

Here’s a simple Go program that accomplishes this. Save it as helm-list-all.go:

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

func main() {
	cmd := exec.Command("helm", "list", "--all-namespaces", "--output", "json")
	output, err := cmd.Output()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error executing helm list: %v\n", err)
		os.Exit(1)
	}

	// For simplicity, we're not parsing JSON here, just printing raw output.
	// A real plugin would parse this into a struct for better formatting.
	fmt.Println("Release Name\tNamespace\tStatus\tChart")
	fmt.Println("------------\t---------\t------\t-----")

	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		if line == "" {
			continue
		}
		// This is a very basic split for demonstration. Real JSON parsing is better.
		// Assuming a simplified output structure for this example.
		// A proper implementation would use encoding/json to unmarshal.
		fields := strings.Fields(line) // This is NOT how you'd parse helm json output
		if len(fields) >= 4 {
			fmt.Printf("%s\t%s\t%s\t%s\n", fields[0], fields[1], fields[2], fields[3])
		} else {
			// Fallback for lines that might not fit the expected format (e.g., empty lines, headers)
			// In a real scenario, you'd handle JSON parsing errors more gracefully.
		}
	}
}

To make this a Helm plugin, you need to:

  1. Build the binary:

    go build -o helm-list-all helm-list-all.go
    

    This creates an executable file named helm-list-all.

  2. Place it in the Helm plugins directory: Helm looks for plugins in ~/.config/helm/plugins/ or $HELM_CONFIG_HOME/plugins/. Create this directory if it doesn’t exist.

    mkdir -p ~/.config/helm/plugins/
    mv helm-list-all ~/.config/helm/plugins/
    
  3. Make it executable:

    chmod +x ~/.config/helm/plugins/helm-list-all
    

Now, you can run your custom command:

helm list-all

This will execute your Go binary, which in turn calls helm list --all-namespaces --output json, and then attempts to display the information.

The core idea is that Helm discovers executables in its plugins directory. When you run helm <subcommand>, Helm checks if an executable named helm-<subcommand> exists in the plugins directory. If it finds one, it executes it, passing any arguments along.

This mechanism allows you to extend Helm’s functionality with any command-line tool or script, but Go is particularly well-suited due to its ease of cross-compilation and integration with system calls.

Your plugin binary receives arguments from the command line. For instance, if you run helm list-all --namespace default, the string --namespace default will be passed as an argument to your main function. You can then use os.Args to access and process these arguments within your plugin. This is how you can build dynamic and interactive plugins that respond to user input.

The most surprising thing about Helm plugins is that they are not limited to Go. While Go is the idiomatic choice due to its straightforward compilation and execution within the Helm ecosystem, you could technically place any executable file (like a Python script, a shell script, or a compiled C program) into the plugins directory, provided it’s executable and named correctly (e.g., helm-myscript for helm myscript). Helm itself doesn’t enforce a language; it merely looks for an executable.

The real power comes when you start to integrate with Helm’s internal APIs or use its libraries. For more complex plugins, you’d typically build a Go application that uses the Helm SDK. This allows you to programmatically interact with Helm’s release management, chart repositories, and other core components. You can then package these Go applications as plugins that offer sophisticated new commands.

To make your plugin discoverable and provide metadata (like its name, description, and version), you can create a plugin.yaml file alongside your executable in the plugins directory. Helm reads this file to understand the plugin’s configuration. For example:

name: list-all
version: 0.1.0
usage: |
  List all helm releases across all namespaces.
description: |
  This plugin provides a consolidated view of all Helm releases.
ignoreFlags: false

This plugin.yaml is crucial for plugins that need to define subcommands or have more complex interactions, though for simple executables, Helm can often infer basic information.

The next logical step is to explore how to use Helm’s Go SDK to build plugins that can programmatically interact with Helm’s internal state, rather than just wrapping existing CLI commands.

Want structured learning?

Take the full Helm course →