# How to Implement a Custom Telegraf Input Plugin from Scratch

> Learn to build custom Telegraf input plugins. Implement the telegraf Input interface, register your plugin, and configure it with TOML settings for seamless data collection.

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

---

**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`](https://github.com/influxdata/telegraf/blob/main/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.

```go
// 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.

```go
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`](https://github.com/influxdata/telegraf/blob/main/sample.conf) content. This appears when users run `telegraf --input-filter mycustom config`.

```go
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.

```go
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.

```go
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.

```go
// 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`](https://github.com/influxdata/telegraf/blob/main/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.

```go
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`](https://github.com/influxdata/telegraf/blob/main/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.

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

  device = "sensor-01"

  ## Request timeout.

  timeout = "30s"

  ## API authentication key.

  api_key = "secret-key-here"

```

The [`README.md`](https://github.com/influxdata/telegraf/blob/main/README.md) follows the standard template found in [`plugins/inputs/example/README.md`](https://github.com/influxdata/telegraf/blob/main/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`](https://github.com/influxdata/telegraf/blob/main/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`](https://github.com/influxdata/telegraf/blob/main/sample.conf) file with `//go:embed` and write a [`README.md`](https://github.com/influxdata/telegraf/blob/main/README.md) matching the format in [`plugins/inputs/example/README.md`](https://github.com/influxdata/telegraf/blob/main/plugins/inputs/example/README.md).
- **Reference the example**: Study [`plugins/inputs/example/example.go`](https://github.com/influxdata/telegraf/blob/main/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.