# How to Develop Custom Filters in the Easegress Pipeline: A Complete Guide

> Learn how to develop custom filters in the Easegress pipeline. This guide covers implementing the filters.Filter interface and registering your custom logic for advanced traffic management.

- Repository: [MegaEase/easegress](https://github.com/megaease/easegress)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Developing a custom filter in Easegress requires implementing the `filters.Filter` interface, registering a `filters.Kind` descriptor in your package's `init()` function, and importing the package in [`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go) so the pipeline can instantiate it.**

The Easegress pipeline processes traffic through a chain of filters that transform, validate, or route requests. When you need custom business logic that the built-in filters do not provide, you can extend the system by developing your own filter in Go. This guide walks through the complete process for developing custom filters in the Easegress pipeline, from package structure to pipeline registration.

## Step 1: Create the Filter Package

Create a new directory under `pkg/filters/<filter-name>` to house your implementation. For example, `pkg/filters/headercounter` contains the reference implementation used throughout this guide. This location follows the standard Easegress project layout and keeps your code alongside the core filter library.

## Step 2: Define the Configuration Spec

Define a `Spec` struct that holds the filter’s configuration fields. The struct uses YAML tags to map pipeline configuration values to Go fields.

```go
type Spec struct {
    Headers []string `yaml:"headers"`
}

```

This struct must implement `filters.Spec`, which is satisfied by embedding the base spec provided by the framework.

## Step 3: Implement the Filter Struct

Create the main filter struct that maintains a reference to `*Spec` and any runtime state such as counters, mutexes, or client connections. The struct serves as the container for your business logic and state management.

```go
type HeaderCounter struct {
    spec *Spec

    countMutex sync.Mutex
    count      map[string]int64
}

```

## Step 4: Implement the filters.Filter Interface

Your filter must satisfy the `filters.Filter` interface defined in [`pkg/filters/filters.go`](https://github.com/megaease/easegress/blob/main/pkg/filters/filters.go). Implement all eight methods to ensure proper lifecycle management and request handling.

**Required methods:**

- **`Name() string`** – Returns `spec.Name()` to identify the filter instance.
- **`Kind() *filters.Kind`** – Returns the package-level `kind` variable describing the filter type.
- **`Spec() filters.Spec`** – Returns the concrete `*Spec` for configuration access.
- **`Init()`** – Prepares runtime state; typically calls a private `reload()` method to initialize maps or connections.
- **`Inherit(previousGeneration filters.Filter)`** – Handles hot updates; usually calls `Init()` or copies state from the previous generation so the filter survives pipeline reloads without dropping connections.
- **`Handle(*context.Context) string`** – Contains the core request-processing logic. Return an empty string to continue normal pipeline flow, or return a custom result string to trigger conditional routing via `jumpIf`.
- **`Status() interface{}`** – Returns optional status information for admin APIs; return `nil` if not needed.
- **`Close()`** – Cleans up goroutines, connections, or file descriptors.

## Step 5: Declare the filters.Kind Metadata

Declare a package-level `filters.Kind` variable that describes your filter to the Easegress registry. This metadata tells the pipeline how to instantiate your filter and what result codes it may return.

```go
const HeaderCounterKind = "HeaderCounter"

var kind = &filters.Kind{
    Name:        HeaderCounterKind,
    Description: "HeaderCounter counts the number of requests which contain the specified header.",
    Results:     []string{},                 // Add custom result codes for jumpIf
    DefaultSpec: func() filters.Spec { return &Spec{} },
    CreateInstance: func(spec filters.Spec) filters.Filter {
        return &HeaderCounter{spec: spec.(*Spec)}
    },
}

```

The `CreateInstance` function acts as a factory, casting the generic spec to your concrete type and returning a configured filter instance.

## Step 6: Register the Filter in init()

Register the kind in the package’s `init()` function so the supervisor’s registry can discover it:

```go
func init() { filters.Register(kind) }

```

This registration makes the filter type available for instantiation by name in pipeline configurations.

## Step 7: Import the Filter in the Global Registry

To include your filter in the compiled binary, add a blank import in [`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go):

```go
import (
    // other imports …
    _ "github.com/megaease/easegress/v2/pkg/filters/headercounter"
)

```

The blank import triggers the `init()` function from Step 6, registering your filter with the global registry without exposing the package’s symbols elsewhere.

## Step 8: Add the Filter to a Pipeline Spec

Reference your custom filter in a pipeline YAML configuration using the `kind` and `name` fields defined in your code:

```yaml
filters:
- kind: HeaderCounter
  name: headerCounter
  headers: ["Cookie", "Authorization"]

```

When the Easegress server loads this configuration, it instantiates your filter using the factory defined in your `filters.Kind`.

## Step 9: Leverage the jumpIf Mechanism (Optional)

If your `Handle` method returns a non-empty string listed in `Kind.Results`, you can use the `jumpIf` directive in your pipeline to branch execution. This is useful for early exits or conditional routing based on your filter’s processing outcome.

## Complete Implementation Example: HeaderCounter Filter

Below is a runnable skeleton that follows all nine steps. This example counts requests containing specific headers and demonstrates proper mutex handling, lifecycle methods, and registration.

```go
package headercounter // import "github.com/megaease/easegress/v2/pkg/filters/headercounter"

import (
    "sync"

    "github.com/megaease/easegress/v2/pkg/context"
    "github.com/megaease/easegress/v2/pkg/filters"
    "github.com/megaease/easegress/v2/pkg/protocols/httpprot"
)

// Spec holds the configuration.
type Spec struct {
    Headers []string `yaml:"headers"`
}

// HeaderCounter implements the filters.Filter interface.
type HeaderCounter struct {
    spec *Spec

    countMutex sync.Mutex
    count      map[string]int64
}

// Name returns the filter instance name.
func (hc *HeaderCounter) Name() string { return hc.spec.Name() }

// Kind returns the filter kind descriptor.
func (hc *HeaderCounter) Kind() *filters.Kind { return kind }

// Spec returns the configuration spec.
func (hc *HeaderCounter) Spec() filters.Spec { return hc.spec }

// Init prepares runtime state.
func (hc *HeaderCounter) Init() { hc.reload() }

// Inherit handles hot updates by re-initializing state.
func (hc *HeaderCounter) Inherit(prev filters.Filter) { hc.Init() }

// Handle processes the request and counts headers.
func (hc *HeaderCounter) Handle(ctx *context.Context) (result string) {
    for _, key := range hc.spec.Headers {
        v := ctx.InputRequest().(*httpprot.Request).HTTPHeader().Get(key)
        if v != "" {
            hc.countMutex.Lock()
            hc.count[key]++
            hc.countMutex.Unlock()
        }
    }
    return "" // Continue normal pipeline flow
}

// Status returns optional admin status (nil if unused).
func (hc *HeaderCounter) Status() interface{} { return nil }

// Close cleans up resources.
func (hc *HeaderCounter) Close() {}

// reload initializes the counter map.
func (hc *HeaderCounter) reload() {
    hc.count = make(map[string]int64)
}

// Kind registration.
const HeaderCounterKind = "HeaderCounter"

var kind = &filters.Kind{
    Name:        HeaderCounterKind,
    Description: "Counts the number of requests containing configured headers.",
    Results:     []string{},
    DefaultSpec: func() filters.Spec { return &Spec{} },
    CreateInstance: func(spec filters.Spec) filters.Filter {
        return &HeaderCounter{spec: spec.(*Spec)}
    },
}

func init() { filters.Register(kind) }

```

Place this file at [`pkg/filters/headercounter/headercounter.go`](https://github.com/megaease/easegress/blob/main/pkg/filters/headercounter/headercounter.go) and import it in [`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go) to activate it.

## Key Source Files to Reference

- **[`pkg/filters/filters.go`](https://github.com/megaease/easegress/blob/main/pkg/filters/filters.go)** – Defines the `Filter` interface and the `Kind` metadata structure used by every filter.
- **[`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go)** – Shows where custom filter packages are imported so the compiled server knows about them.
- **[`docs/06.Development-for-Easegress/6.1.Developer-Guide.md`](https://github.com/megaease/easegress/blob/main/docs/06.Development-for-Easegress/6.1.Developer-Guide.md)** – Contains the full walkthrough of filter creation, registration, and pipeline integration.

## Summary

- **Package Structure** – Create your filter under `pkg/filters/<name>` following the standard layout.
- **Interface Compliance** – Implement all eight methods of `filters.Filter` including `Handle`, `Init`, and `Inherit` for hot-reload support.
- **Metadata Declaration** – Define a `filters.Kind` with factory function and register it in `init()`.
- **Binary Integration** – Use blank imports in [`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go) to include your package in the build.
- **Pipeline Usage** – Reference the filter by `kind` in YAML configuration; return custom strings from `Handle` to enable `jumpIf` routing.

## Frequently Asked Questions

### What Go interface must a custom Easegress filter implement?

A custom filter must implement the `filters.Filter` interface defined in [`pkg/filters/filters.go`](https://github.com/megaease/easegress/blob/main/pkg/filters/filters.go). This includes eight methods: `Name()`, `Kind()`, `Spec()`, `Init()`, `Inherit()`, `Handle()`, `Status()`, and `Close()`. The `Handle` method receives a `*context.Context` and returns a string result code that controls pipeline flow.

### How do I register a custom filter so the Easegress pipeline can discover it?

Declare a `filters.Kind` variable describing your filter, then call `filters.Register(kind)` inside your package’s `init()` function. Finally, add a blank import of your package in [`pkg/registry/registry.go`](https://github.com/megaease/easegress/blob/main/pkg/registry/registry.go). This registration pattern allows the supervisor to instantiate your filter by name when parsing pipeline configurations.

### Can custom filters return result codes to control pipeline execution flow?

Yes. If your `Handle` method returns a non-empty string declared in `Kind.Results`, the pipeline can use the `jumpIf` directive to branch to a labeled filter or exit early. Return an empty string to continue normal sequential processing through the pipeline.

### Where should I store runtime state that must survive configuration reloads?

Store runtime state in fields of your filter struct (e.g., counters, connection pools). Implement the `Inherit` method to copy state from the previous generation during hot updates, or simply call `Init()` if your filter can safely reinitialize. This ensures zero-downtime configuration changes while preserving accumulated state.