# How Trivy’s Plugin System Extends Security Scanning Capabilities

> Discover how Trivy’s plugin system enhances security scanning. Add new scanners and output formats easily using external binaries without core code changes.

- Repository: [Aqua Security/trivy](https://github.com/aquasecurity/trivy)
- Tags: internals
- Published: 2026-03-23

---

**Trivy’s plugin system delegates specific detection tasks to external binaries through a lightweight, language-agnostic framework that uses JSON communication over STDIN/STDOUT, allowing the community to add new scanners and output formats without modifying the core codebase.**

The **aquasecurity/trivy** repository implements a plugin architecture modeled after `kubectl` and `helm` that keeps the main binary small while enabling limitless extensibility. By isolating functionality into independent processes, the Trivy plugin system supports custom scanners, specialized output formats, and third-party integrations written in any programming language.

## Core Architecture and Components

The plugin implementation in [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go) defines several key abstractions that work together to discover, manage, and execute extensions.

### The Plugin Type and Metadata

At the heart of the system is the **`Plugin`** struct, which holds metadata including the plugin name, version, command, description, and supported scanner types. This definition also tracks the executable path to the plugin binary on the local filesystem. When Trivy initializes, it reads **[`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml)** manifest files from the plugin cache directory—typically `$XDG_DATA_HOME/.trivy/plugins` or `~/.trivy/plugins`—and unmarshals them into these structures.

### Plugin Manager

The **plugin manager** handles the lifecycle of all installed extensions. Implemented in the same [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go) file, it loads plugin manifests, validates their schema, and registers them in a map keyed by name for fast lookup. The manager also resolves plugin names to their executable paths when Trivy needs to invoke them during scans.

### Centralized Plugin Index

Trivy maintains a public catalog called **`trivy-plugin-index`** (hosted in a separate repository) that lists available plugins, their versions, and download URLs. When users run `trivy plugin update`, Trivy syncs this index to the local cache, enabling discovery of community-contributed extensions without manual searching.

### CLI Integration

The command-line interface provides thin wrappers around the manager through the `trivy plugin` subcommand group. According to the CLI reference in [`docs/guide/references/configuration/cli/trivy_plugin.md`](https://github.com/aquasecurity/trivy/blob/main/docs/guide/references/configuration/cli/trivy_plugin.md), supported operations include:

- `install` – Downloads and installs a plugin from the index
- `list` – Shows locally installed plugins
- `search` – Queries the index for available plugins
- `run` – Executes a specific plugin directly
- `info` – Displays metadata about an installed plugin
- `uninstall` – Removes a plugin from the cache
- `upgrade` – Updates a plugin to the latest version
- `update` – Refreshes the local copy of the plugin index

## Plugin Discovery and Execution Flow

The Trivy plugin system follows a standardized five-step execution model that ensures consistent behavior across all extensions.

1. **Discovery** – On startup, Trivy scans the plugin cache directory for [`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml) files. Each manifest registers a plugin with its command line arguments, supported scanners, and human-readable description.

2. **Selection** – Users activate plugins via CLI flags such as `--output-plugin` or through explicit subcommands like `trivy plugin run <name>`. The manager resolves these references to absolute executable paths.

3. **Invocation** – When a scan reaches a step handled by a plugin, Trivy spawns the plugin binary as an isolated process. It streams a JSON payload containing scan results or input data to the plugin’s **STDIN**.

4. **Result Handling** – The plugin processes the input and returns a JSON response on **STDOUT**. Trivy reads this output, merges the results into its own data structures, and continues with the remaining scan workflow.

5. **Lifecycle Management** – Commands like `install`, `uninstall`, and `upgrade` manipulate the plugin cache and index, ensuring reproducible environments across different machines.

This architecture allows plugins to function as black boxes—Trivy does not need to know the implementation language or internal logic, only the JSON contract defined in the `Plugin` interface.

## Installing and Managing Plugins

Working with the Trivy plugin system begins with synchronizing the local index and installing desired extensions.

Refresh the plugin catalog to ensure access to the latest community contributions:

```bash
trivy plugin update

```

Search for specific functionality, such as referrer capabilities:

```bash
trivy plugin search referrer

```

Install the official referrer plugin, which downloads the binary into the versioned cache directory at `$XDG_DATA_HOME/.trivy/plugins/referrer_<version>`:

```bash
trivy plugin install referrer

```

List installed plugins to verify successful installation:

```bash
trivy plugin list

```

## Running Plugins in Scans

Plugins integrate seamlessly into standard Trivy workflows through CLI flags or direct execution.

Execute the referrer plugin directly against an image reference:

```bash
trivy plugin run referrer \
    --name my-image \
    --type image \
    --arg my-registry.example.com/my-image:latest

```

Use an output plugin to customize result formatting during a normal image scan:

```bash
trivy image --output-plugin json \
    --output-plugin-arg '{"indent":2}' \
    nginx:latest

```

Custom output plugins follow the same interface—reference them by name using `--output-plugin my-custom` to process findings through external formatting logic.

## Programmatic Plugin Integration

Go developers can interact with the plugin system directly using the internal packages exposed in [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go).

The following example demonstrates loading the plugin manager, resolving a specific plugin, and executing it with custom input:

```go
import (
    "encoding/json"
    "github.com/aquasecurity/trivy/pkg/plugin"
    "github.com/aquasecurity/trivy/pkg/scanner"
)

func runCustomPlugin() error {
    // Initialize manager and load plugins from cache
    pm, err := plugin.NewManager()
    if err != nil { return err }

    // Resolve plugin by name
    plug, err := pm.Get("referrer")
    if err != nil { return err }

    // Prepare JSON input payload
    input := []byte(`{"image":"nginx:latest"}`)

    // Execute plugin and capture STDOUT
    out, err := plug.Run(input)
    if err != nil { return err }

    // Process plugin response
    var result scanner.Results
    if err := json.Unmarshal(out, &result); err != nil {
        return err
    }
    
    return nil
}

```

The `plugin.Manager` handles cache validation, while the `Plugin.Run()` method manages the STDIN/STDOUT streaming and process lifecycle.

## Summary

- **Trivy’s plugin system** uses external binaries to extend core scanning capabilities while maintaining a small, focused main executable.
- The architecture centers on the **`Plugin`** type in [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go), the plugin manager, and the public `trivy-plugin-index` catalog.
- Plugins communicate via **JSON over STDIN/STDOUT**, enabling language-agnostic implementations.
- The CLI provides comprehensive lifecycle management through `trivy plugin install|run|update|uninstall` commands.
- Plugins are stored in `$XDG_DATA_HOME/.trivy/plugins` with versioned subdirectories and [`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml) manifests.

## Frequently Asked Questions

### How does Trivy discover installed plugins at runtime?

Trivy scans the plugin cache directory—defaulting to `$XDG_DATA_HOME/.trivy/plugins` or `~/.trivy/plugins` on startup—looking for [`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml) manifest files. Each valid manifest is parsed into a `Plugin` struct and registered in the plugin manager’s internal map, keyed by plugin name for quick resolution during command execution.

### Can plugins be written in languages other than Go?

Yes. Because plugins execute as isolated processes communicating through JSON streams on STDIN and STDOUT, they can be implemented in any language that handles JSON I/O. The plugin binary only needs to adhere to the JSON contract defined in the Trivy documentation and return properly formatted results on STDOUT.

### What is the difference between `trivy plugin update` and `trivy plugin upgrade`?

The `update` command refreshes the local copy of the `trivy-plugin-index` repository, downloading the latest catalog of available plugins and versions. The `upgrade` command, by contrast, updates an already-installed plugin to its latest version by downloading the new binary and replacing the cached executable while preserving the plugin configuration.

### Where does Trivy store plugin binaries and metadata?

Trivy stores plugins in a cache directory following the XDG Base Directory Specification—typically `$XDG_DATA_HOME/.trivy/plugins` or falling back to `~/.trivy/plugins`. Each plugin resides in a versioned subdirectory containing the executable binary and its [`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml) manifest file defining metadata and supported scanner types.