How to Implement a Custom Telegraf Input Plugin from Scratch

A custom Telegraf input plugin must implement the telegraf.Input interface, register itself via inputs.Add() in an init() function, and expose TOML-tagged configuration fields to receive settings from the user.

Creating a custom input plugin for InfluxData Telegraf allows you to ingest metrics from proprietary APIs or internal systems. The plugin architecture relies on a minimal Go interface defined in the core package, making it possible to add new data sources without modifying Telegraf's internals. This guide walks through the exact implementation patterns found in the influxdata/telegraf repository, from the interface contract to configuration handling.

Understanding the Core Input Interface

Every input plugin in Telegraf satisfies the interface defined in input.go. The contract requires two primary methods: Gather for data collection and SampleConfig for documentation.

The Gather method receives an telegraf.Accumulator and returns an error. This method executes on every collection interval—typically every 10 seconds—and writes metrics to the accumulator. The optional Init() method, also defined in the interface, runs once at startup to validate configuration and set defaults.

// From telegraf/input.go
type Input interface {
    Init() error
    SampleConfig() string
    Gather(telegraf.Accumulator) error
}

Telegraf's plugin loader checks for these signatures during startup. If your struct implements them and registers with the global registry, Telegraf will instantiate and run it.

Setting Up the Plugin Directory Structure

Telegraf organizes plugins under plugins/inputs/<plugin_name>/. A minimal implementation requires three files: the Go source, a sample configuration, and documentation.


plugins/
└─ inputs/
   └─ mycustom/
      ├─ mycustom.go          # Main implementation

      ├─ sample.conf          # Default configuration (embedded)

      └─ README.md            # User documentation

Place your new directory alongside built-in plugins like plugins/inputs/example/, which serves as the official reference implementation in the repository.

Implementing the Plugin Skeleton

Start by defining a struct that holds configuration fields and internal state. Use TOML struct tags to map configuration keys to fields. Telegraf automatically unmarshals the [[inputs.mycustom]] section of the config file into this struct.

package mycustom

import (
    _ "embed"
    "errors"
    "time"

    "github.com/influxdata/telegraf"
    "github.com/influxdata/telegraf/config"
    "github.com/influxdata/telegraf/plugins/inputs"
)

//go:embed sample.conf
var sampleConfig string

type MyCustom struct {
    Device  string          `toml:"device"`
    Timeout config.Duration `toml:"timeout"`
    APIKey  config.Secret   `toml:"api_key"`
    Log     telegraf.Logger `toml:"-"`
}

The SampleConfig() method returns the embedded sample.conf content. This appears when users run telegraf --input-filter mycustom config.

func (m *MyCustom) SampleConfig() string {
    return sampleConfig
}

The Init() method validates mandatory fields and establishes defaults. Access the logger via the Log field to write debug or info messages.

func (m *MyCustom) Init() error {
    if m.Device == "" {
        return errors.New("device must be set")
    }
    if m.Timeout == 0 {
        m.Timeout = config.Duration(30 * time.Second)
    }
    m.Log.Infof("Initializing plugin for device: %s", m.Device)
    return nil
}

The Gather() method contains the core collection logic. Create maps for tags and fields, then call acc.AddFields() to emit metrics.

func (m *MyCustom) Gather(acc telegraf.Accumulator) error {
    fields := map[string]interface{}{
        "status": 1,
        "temp":   42.0,
    }
    tags := map[string]string{
        "device": m.Device,
    }
    acc.AddFields("mycustom_sensor", fields, tags)
    return nil
}

Handling Secrets and Duration Types

Telegraf provides specialized types in config/ for sensitive data and time durations. Use config.Secret for API keys or passwords. This type integrates with Telegraf's secret-store and prevents credentials from appearing in log files or debug output.

// Retrieving the secret value in Init() or Gather()
key, err := m.APIKey.Get()
if err != nil {
    return err
}
defer key.Destroy() // Securely clear from memory

For timeout or interval fields, use config.Duration instead of time.Duration. This type supports TOML strings like "5m" or "100ms" and converts them automatically.

Registering the Plugin with the Global Registry

The final step requires registering the plugin so Telegraf can discover it. In plugins/inputs/inputs.go, the package maintains a global registry. Your plugin calls inputs.Add() inside an init() function to insert a factory function that returns a new instance.

func init() {
    inputs.Add("mycustom", func() telegraf.Input {
        return &MyCustom{}
    })
}

When Telegraf starts, it scans the plugins/inputs/ directory (or your custom import path), executes all init() functions, and populates the registry with available inputs. Users can then reference the plugin by name in their configuration file.

Creating Configuration and Documentation

The sample.conf file contains the default TOML configuration. Telegraf embeds this content into the binary using the //go:embed directive and displays it via CLI tools.

[[inputs.mycustom]]
  ## Device identifier (required).

  device = "sensor-01"

  ## Request timeout.

  timeout = "30s"

  ## API authentication key.

  api_key = "secret-key-here"

The README.md follows the standard template found in plugins/inputs/example/README.md. It must include a description, global configuration options, the sample configuration (using @sample.conf), and a metric table defining tags and fields. This documentation renders in Telegraf's help output and on the GitHub repository.

Summary

  • Implement telegraf.Input: Define Gather(), SampleConfig(), and optionally Init() in a struct tagged with TOML annotations.
  • Register via inputs.Add(): Place an init() function in your package that calls inputs.Add("name", factory) to register with the global plugin registry in plugins/inputs/inputs.go.
  • Use typed configuration: Employ config.Duration for time values and config.Secret for credentials to leverage Telegraf's built-in parsing and security features.
  • Embed documentation: Include a sample.conf file with //go:embed and write a README.md matching the format in plugins/inputs/example/README.md.
  • Reference the example: Study plugins/inputs/example/example.go for a minimal, working implementation that compiles against the current Telegraf codebase.

Frequently Asked Questions

Do I need to fork Telegraf to use a custom input plugin?

No. You can maintain your plugin in a separate repository and import it into a custom Telegraf build. Import your package in a main.go file alongside the standard Telegraf imports, or place your code in plugins/inputs/<name>/ and rebuild from source using go build ./....

What is the difference between Init() and Gather() in a Telegraf input plugin?

Init() executes once when Telegraf starts, making it ideal for validating configuration, establishing connections, and setting defaults. Gather() executes on every collection interval—typically every 10 seconds—and contains the logic to fetch metrics and push them to the accumulator.

How does Telegraf handle configuration parsing for custom plugins?

Telegraf uses the github.com/BurntSushi/toml library to unmarshal configuration sections into your struct. Fields tagged with toml:"field_name" map directly to keys in the [[inputs.your_plugin]] configuration block. The parser automatically converts types like int64, bool, config.Duration, and config.Secret based on the struct field types.

How do I prevent API keys from appearing in Telegraf logs?

Store sensitive values in config.Secret struct fields. When you need the plain value, call .Get() which returns a protected *config.SecretValue that you must destroy after use with defer value.Destroy(). Telegraf's secret-store integration ensures these values are masked in debug output and configuration dumps.

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 →