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

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 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, triggered by the entry point in 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 and 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:

// 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:

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

Enable in telegraf.conf:

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

Using Execd with Python for Output Plugins

Create an executable script that reads JSON lines from stdin:

#!/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:

[[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; unhandled panics can crash the entire process. External shared-object plugins offer partial protection through panic recovery mechanisms in 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 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 at compile-time.
  • External shared libraries are loaded dynamically from --plugin-directory using plugin.Open in internal/goplugin/plugin.go and must export a Plugin symbol.
  • Execd plugins run as subprocesses managed by plugins/inputs/execd/execd.go and 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →