# How External Plugins Work in Telegraf Compared to Built-In Plugins

> Discover how external Telegraf plugins load at runtime versus built-in plugins compiled directly into the agent binary. Understand Telegraf plugin architecture for improved data collection.

- Repository: [InfluxData/telegraf](https://github.com/influxdata/telegraf)
- Tags: deep-dive
- Published: 2026-05-14

---

**External plugins in Telegraf are loaded dynamically at runtime from shared libraries or external processes, while built-in plugins are compiled directly into the agent binary and registered at compile-time via `init()` functions.**

The Telegraf plugin architecture in the `influxdata/telegraf` repository supports two distinct execution models to balance performance with extensibility. While built-in plugins ship with the official binary and execute within the same memory space, external plugins enable custom extensions without rebuilding the core agent. Understanding these architectural differences helps operators choose the appropriate method for extending metric collection, processing, and output capabilities.

## Built-In vs External Plugins: Key Differences

Built-in plugins are native Go code compiled directly into the Telegraf binary. They register themselves through `init()` functions located in [`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go) and other importer files, making them available immediately when the agent starts. In contrast, external plugins are discovered at runtime from a user-specified directory using the `--plugin-directory` flag, requiring dynamic loading via Go’s `plugin.Open` mechanism or execution as separate processes.

| Feature | Built-In Plugins | External Plugins |
|---------|------------------|------------------|
| **Compilation** | Compiled into `telegraf` binary | Built as `.so`/`.dll` shared objects or standalone executables |
| **Registration** | `init()` functions at compile-time | Discovered via `--plugin-directory` at runtime |
| **Isolation** | Shared memory space; panics crash the agent | Process isolation with `execd` or recovered panics in shared objects |
| **Languages** | Go only | Go (shared libraries) or any language via `execd` |

## How External Plugins Are Loaded at Runtime

Telegraf discovers and initializes external plugins through a specific loading sequence implemented in [`internal/goplugin/plugin.go`](https://github.com/influxdata/telegraf/blob/main/internal/goplugin/plugin.go), triggered by the entry point in [`cmd/telegraf/main.go`](https://github.com/influxdata/telegraf/blob/main/cmd/telegraf/main.go).

### Shared Object Discovery and Registration

When the agent starts with the `--plugin-directory` flag (defaulting to `/etc/telegraf/plugins`), the `LoadExternalPlugins` function scans for platform-specific shared libraries (`.so` on Linux, `.dll` on Windows, or `.dylib` on macOS). For each candidate file, Telegraf invokes `plugin.Open` to load the module and searches for an exported `Plugin` symbol that implements one of the standard interfaces (`telegraf.Input`, `telegraf.Output`, `telegraf.Processor`, or `telegraf.Aggregator`).

After type-asserting the symbol to the appropriate interface, Telegraf registers the instance identically to built-in plugins. During each collection cycle, the agent calls the same lifecycle methods—`Init()`, `Gather()`, `SampleConfig()`, and `Description()`—but executes code residing in the separately loaded module. The core recovers panics from external plugins to prevent a single faulty module from crashing the entire agent.

### Execd Process-Based Isolation

For languages that cannot compile to Go plugins or when complete process isolation is required, Telegraf provides the `execd` wrapper. Implemented in [`plugins/inputs/execd/execd.go`](https://github.com/influxdata/telegraf/blob/main/plugins/inputs/execd/execd.go) and [`plugins/outputs/execd/execd.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/execd/execd.go), this mechanism spawns the external executable as a subprocess. The wrapper sends the full TOML configuration block as JSON to the process on startup via stdin, then communicates via line-protocol for metric data—inputs read lines from stdout, while outputs write metric lines to stdin. This architecture ensures that crashes in the external process cannot terminate the Telegraf agent.

## Creating External Plugins: Practical Implementation

### Building a Go Shared Library Plugin

External Go plugins must implement the Telegraf interfaces and export a `Plugin` variable. Compile using the Go plugin build mode:

```go
// myinput.go
package main

import (
    "github.com/influxdata/telegraf"
    "github.com/influxdata/telegraf/plugins/inputs"
)

type MyInput struct{}

func (m *MyInput) Gather(acc telegraf.Accumulator) error {
    acc.AddGauge("my_metric", map[string]interface{}{"value": 42}, nil)
    return nil
}

func (m *MyInput) SampleConfig() string { return "" }
func (m *MyInput) Description() string { return "My external input plugin" }

var Plugin MyInput

func init() {
    inputs.Add("myinput", func() telegraf.Input { return &Plugin })
}

```

Build and deploy the shared object:

```bash
go build -buildmode=plugin -o myinput.so myinput.so
cp myinput.so /etc/telegraf/plugins/

```

Enable in [`telegraf.conf`](https://github.com/influxdata/telegraf/blob/main/telegraf.conf):

```toml
[[inputs.myinput]]
  interval = "10s"

```

### Using Execd with Python for Output Plugins

Create an executable script that reads JSON lines from stdin:

```python
#!/usr/bin/env python3

# myoutput.py

import sys, json

while True:
    line = sys.stdin.readline()
    if not line:
        break
    metric = json.loads(line)
    # Forward to external API or database

    print(json.dumps({"status":"sent"}))
    sys.stdout.flush()

```

Make the script executable and configure Telegraf:

```toml
[[outputs.execd]]
  command = ["python3", "/opt/telegraf/myoutput.py"]
  data_format = "json"

```

## Isolation and Lifecycle Management

Built-in plugins run in the same memory space as the Telegraf agent according to the registration logic in [`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go); unhandled panics can crash the entire process. External shared-object plugins offer partial protection through panic recovery mechanisms in [`internal/goplugin/plugin.go`](https://github.com/influxdata/telegraf/blob/main/internal/goplugin/plugin.go), while `execd` plugins provide complete isolation through process boundaries. When external plugin support is disabled at compile time, [`internal/goplugin/noplugin.go`](https://github.com/influxdata/telegraf/blob/main/internal/goplugin/noplugin.go) provides a no-op stub that returns early.

Both plugin types receive configuration through identical interfaces. Shared-object plugins unmarshal TOML blocks directly into their structs, while `execd` plugins receive the configuration as JSON on startup before the line-protocol metric stream begins.

## Summary

- **Built-in plugins** are compiled into the Telegraf binary and register via `init()` functions in files like [`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go) at compile-time.
- **External shared libraries** are loaded dynamically from `--plugin-directory` using `plugin.Open` in [`internal/goplugin/plugin.go`](https://github.com/influxdata/telegraf/blob/main/internal/goplugin/plugin.go) and must export a `Plugin` symbol.
- **Execd plugins** run as subprocesses managed by [`plugins/inputs/execd/execd.go`](https://github.com/influxdata/telegraf/blob/main/plugins/inputs/execd/execd.go) and [`plugins/outputs/execd/execd.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/execd/execd.go), communicating via stdin/stdout to provide crash isolation.
- Both plugin types implement identical interfaces (`Input`, `Output`, `Processor`, `Aggregator`) and receive configuration through the same mechanisms.
- External plugins require building with `go build -buildmode=plugin` or writing executables that follow the execd JSON startup protocol and line-protocol metric streaming.

## Frequently Asked Questions

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

Yes. While Go shared libraries require compilation with `-buildmode=plugin`, the `execd` wrapper supports any language that can read stdin and write stdout. Python, Ruby, or compiled binaries can function as external plugins through this subprocess mechanism, receiving configuration as JSON and metrics as line-protocol.

### Where does Telegraf look for external plugins by default?

Telegraf scans the directory specified by the `--plugin-directory` command-line flag, which defaults to `/etc/telegraf/plugins`. The loader implementation in [`internal/goplugin/plugin.go`](https://github.com/influxdata/telegraf/blob/main/internal/goplugin/plugin.go) specifically searches for files with platform-specific extensions like `.so` on Linux or `.dll` on Windows.

### Do external plugins have access to the same configuration options as built-in plugins?

Yes. Both plugin types utilize the same configuration interfaces. Shared-object plugins receive their TOML configuration block unmarshaled into their struct fields, while `execd` plugins receive the full configuration as a JSON object written to their stdin immediately upon process startup, followed by the metric stream.

### What happens if an external plugin crashes?

For shared-object plugins, the Telegraf core recovers panics to prevent agent termination, though memory corruption could still affect stability. For `execd` plugins, crashes are isolated to the subprocess; Telegraf can restart the process or log the failure without affecting other plugins or the main agent, as implemented in the `execd` wrapper logic.