# How to Implement a Custom Telegraf Output Plugin: Complete Go Guide

> Learn how to implement a custom Telegraf output plugin in Go. This guide covers creating structs satisfying the telegraf.Output interface and registering your plugin with outputs.Add().

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

---

**To implement a custom Telegraf output plugin, create a Go struct that satisfies the `telegraf.Output` interface defined in [`output.go`](https://github.com/influxdata/telegraf/blob/main/output.go) by implementing `Connect()`, `Write()`, and `Close()` methods, provide configuration via `SampleConfig()`, and register the plugin using `outputs.Add()` in an `init()` function.**

Telegraf is the open-source, plugin-driven metrics collection agent maintained by InfluxData. When you need to send metrics to a proprietary API or unsupported datastore, you can extend the agent by implementing a custom Telegraf output plugin that conforms to the core interfaces in the `influxdata/telegraf` repository.

## Understanding the telegraf.Output Interface

Every output plugin must satisfy the `Output` interface defined in **[`output.go`](https://github.com/influxdata/telegraf/blob/main/output.go)**. This contract ensures Telegraf can manage the plugin lifecycle uniformly while handling buffering and retries automatically.

```go
type Output interface {
    PluginDescriber          // required for SampleConfig()
    Connect() error          // called once when the plugin starts
    Close() error            // called once on shutdown (after all writes)
    Write(metrics []Metric) error // called repeatedly with metric batches
}

```

The interface embeds `PluginDescriber` (from **[`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go)**) which requires a `SampleConfig()` method that returns a TOML configuration string.

### Core Lifecycle Methods

The runtime in **[`models/running_output.go`](https://github.com/influxdata/telegraf/blob/main/models/running_output.go)** invokes three primary methods that you must implement:

- **`Connect()`** – Opens connections, allocates resources, and prepares the destination. Called once when the plugin starts.

- **`Write(metrics []Metric)`** – Receives batches of metrics for export. Called repeatedly during operation. Return an error to signal write failure and trigger retry logic.

- **`Close()`** – Gracefully shuts down connections and cleans up resources. Called once when Telegraf stops.

## Step-by-Step Implementation Guide

Follow these steps to create a production-ready custom output plugin in the `influxdata/telegraf` codebase.

### 1. Create the Plugin Directory

Add a new directory under `plugins/outputs/<myplugin>/` and declare `package <myplugin>` in your Go files. This mirrors the structure used by the reference implementation in **[`plugins/outputs/file/file.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/file/file.go)**.

### 2. Define the Plugin Struct

Create a struct that holds configuration fields (tagged with TOML) and runtime objects like loggers or clients.

```go
type MyOutput struct {
    Endpoint string `toml:"endpoint"`
    APIKey   string `toml:"api_key"`
    Log      telegraf.Logger `toml:"-"` // injected by the runtime
}

```

### 3. Implement PluginDescriber

Add a `SampleConfig()` method that returns a string containing the default TOML configuration. Telegraf uses this to generate configuration examples.

```go
func (m *MyOutput) SampleConfig() string {
    return `
  ## Endpoint URL

  endpoint = "https://api.example.com/metrics"
  ## API authentication key

  api_key = "secret"
`
}

```

### 4. Implement Connect

Add resource initialization logic. Return an error if the destination is unreachable.

```go
func (m *MyOutput) Connect() error {
    // Initialize HTTP client, open database connection, etc.
    return nil
}

```

### 5. Implement Write

Process the metric batch. Iterate over `[]telegraf.Metric`, serialize if necessary, and write to your destination.

```go
func (m *MyOutput) Write(metrics []telegraf.Metric) error {
    for _, metric := range metrics {
        // Export logic here
    }
    return nil
}

```

### 6. Implement Close

Clean up connections and resources to prevent goroutine leaks.

```go
func (m *MyOutput) Close() error {
    // Close connections, flush buffers
    return nil
}

```

### 7. (Optional) Implement Initializer

If you need validation before `Connect()`, implement the `Initializer` interface from **[`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go)**:

```go
func (m *MyOutput) Init() error {
    // Validate configuration
    return nil
}

```

### 8. Register the Plugin

In an `init()` function, call `outputs.Add()` to make your plugin discoverable. This registration pattern appears in every built-in output, including **[`plugins/outputs/file/file.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/file/file.go)**.

```go
func init() {
    outputs.Add("myplugin", func() telegraf.Output {
        return &MyOutput{}
    })
}

```

## Complete Working Example: Stdout Output

Below is a compile-ready custom Telegraf output plugin that writes metrics to standard output in InfluxDB line protocol format. Save this as [`plugins/outputs/stdout/stdout.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/stdout/stdout.go).

```go
package stdout

import (
	"fmt"
	"os"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/plugins/outputs"
)

//go:embed sample.conf
var sampleConfig string

type Stdout struct {
	Log telegraf.Logger `toml:"-"` // injected logger
}

func (*Stdout) SampleConfig() string { return sampleConfig }

func (s *Stdout) Connect() error { return nil }

func (s *Stdout) Close() error { return nil }

func (s *Stdout) Write(metrics []telegraf.Metric) error {
	for _, m := range metrics {
		line, err := m.MarshalLineProtocol()
		if err != nil {
			s.Log.Errorf("failed to serialize metric: %v", err)
			continue
		}
		if _, err = fmt.Fprintln(os.Stdout, string(line)); err != nil {
			s.Log.Errorf("failed to write metric: %v", err)
			return err
		}
	}
	return nil
}

func init() {
	outputs.Add("stdout", func() telegraf.Output {
		return &Stdout{}
	})
}

```

Include a [`sample.conf`](https://github.com/influxdata/telegraf/blob/main/sample.conf) file in the same directory:

```toml

## Example configuration for the stdout output

# no configuration required

```

After building Telegraf, reference the plugin in your configuration:

```toml
[[outputs.stdout]]

```

## Optional Interfaces and Advanced Features

Beyond the core `Output` interface, the `influxdata/telegraf` repository defines several optional interfaces in **[`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go)** that enhance functionality:

- **`Initializer`** – Provides an `Init()` error method for pre-connection validation.

- **`SetSerializer`** – Allows the plugin to receive a `telegraf.Serializer` for encoding metrics (used when the output supports multiple formats).

- **`PluginWithID`** – Enables unique identification for the plugin instance.

Telegraf's runtime automatically detects these interfaces via type assertions and invokes their methods when present.

## Plugin Lifecycle and Runtime Behavior

Understanding how the core executes your plugin helps you write efficient, fault-tolerant code. The **[`models/running_output.go`](https://github.com/influxdata/telegraf/blob/main/models/running_output.go)** file contains the `RunningOutput` struct, which wraps your plugin and manages:

- **Buffering** – Accumulating metrics before calling `Write()` to reduce I/O overhead.

- **Retry Logic** – Re-invoking `Write()` if the method returns an error, respecting the agent's retry limits.

- **Metrics Tracking** – Recording write statistics without plugin intervention.

- **Logger Injection** – Setting the `Log` field via reflection if the struct contains a `telegraf.Logger` field tagged with `toml:"-"`.

When Telegraf starts, it instantiates your plugin via the factory function registered with `outputs.Add()`, calls `Init()` if implemented, then `Connect()`, and enters the write loop. During shutdown, it calls `Close()` after flushing any buffered data.

## Summary

- **Implement the `telegraf.Output` interface** defined in **[`output.go`](https://github.com/influxdata/telegraf/blob/main/output.go)** with `Connect()`, `Write()`, and `Close()` methods.

- **Embed configuration** by implementing `SampleConfig()` from the `PluginDescriber` interface in **[`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go)**.

- **Register in `init()`** using `outputs.Add()` to make the plugin available to the Telegraf agent.

- **Place code** in `plugins/outputs/<name>/` following the structure of **[`plugins/outputs/file/file.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/file/file.go)**.

- **Let the runtime handle** buffering, retries, and statistics via **[`models/running_output.go`](https://github.com/influxdata/telegraf/blob/main/models/running_output.go)**.

## Frequently Asked Questions

### How do I test my custom output plugin without building all of Telegraf?

You can run unit tests within your plugin directory using `go test ./plugins/outputs/<myplugin>/...`. For integration testing, build Telegraf with your plugin included by ensuring your package is imported in **[`plugins/outputs/all/all.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/all/all.go)** (which is typically auto-generated), then run the binary with a configuration file referencing your output.

### Can my custom output plugin use Telegraf's built-in serializers?

Yes. Implement the **`SetSerializer`** interface (defined in **[`plugin.go`](https://github.com/influxdata/telegraf/blob/main/plugin.go)**) by adding a `SetSerializer(serializer telegraf.Serializer)` method to your struct. Telegraf's plugin loader will inject the appropriate serializer based on the `data_format` configuration option, allowing you to reuse JSON, InfluxDB line protocol, or other formats without custom encoding logic.

### What happens if my Write method returns an error?

When `Write()` returns an error, the `RunningOutput` in **[`models/running_output.go`](https://github.com/influxdata/telegraf/blob/main/models/running_output.go)** retains the unwritten metrics in its buffer and retries the write according to the agent's `metric_buffer_limit` and retry settings. If retries are exhausted or the buffer fills, metrics may be dropped depending on your configuration, and an error is logged via the injected logger.

### Where should I place my custom output plugin code in the repository?

Place your plugin source in `plugins/outputs/<pluginname>/` within the Telegraf repository. Include a [`README.md`](https://github.com/influxdata/telegraf/blob/main/README.md) documenting configuration options and a [`sample.conf`](https://github.com/influxdata/telegraf/blob/main/sample.conf) file. The registration in your `init()` function automatically integrates the plugin into the build system, which discovers packages via **[`plugins/outputs/all/all.go`](https://github.com/influxdata/telegraf/blob/main/plugins/outputs/all/all.go)**.