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: DefineGather(),SampleConfig(), and optionallyInit()in a struct tagged with TOML annotations. - Register via
inputs.Add(): Place aninit()function in your package that callsinputs.Add("name", factory)to register with the global plugin registry inplugins/inputs/inputs.go. - Use typed configuration: Employ
config.Durationfor time values andconfig.Secretfor credentials to leverage Telegraf's built-in parsing and security features. - Embed documentation: Include a
sample.conffile with//go:embedand write aREADME.mdmatching the format inplugins/inputs/example/README.md. - Reference the example: Study
plugins/inputs/example/example.gofor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →