How frp's Feature Gate System Enables Experimental Features: A Deep Dive

frp implements a thread-safe feature gate mechanism that lets operators toggle experimental functionality at runtime through configuration maps, ensuring alpha-stage code remains inaccessible unless explicitly enabled.

The fatedier/frp repository uses a centralized policy layer to safely ship unstable capabilities without risking production stability. The entire system resides in pkg/policy/featuregate/feature_gate.go and provides a declarative API for registering, configuring, and querying feature states across the client and server.

Core Architecture of the Feature Gate System

At the heart of frp's implementation are four core abstractions that define how experimental capabilities are staged and controlled.

Feature Identifiers and Maturity Stages

Each capability is identified by a Feature string constant and assigned a FeatureStage representing its maturity level:

  • ALPHA: Disabled by default, requires explicit opt-in
  • BETA: May be enabled by default depending on the specification
  • GA (General Availability): Always enabled and hidden from the gate list

The FeatureSpec struct captures this metadata along with the default boolean state and whether the gate is locked to that default. According to the source code, the global singleton DefaultFeatureGates manages these definitions through a map called defaultFeatures located at lines 56-59 of feature_gate.go.

For example, the experimental VirtualNet capability is declared as:

VirtualNet = Feature("VirtualNet")

defaultFeatures = map[Feature]FeatureSpec{
    VirtualNet: {Default: false, Stage: Alpha},
}

Because VirtualNet is marked as Alpha with a default of false, the system blocks its usage until an administrator explicitly overrides the setting.

The MutableFeatureGate Interface

The system exposes MutableFeatureGate, which extends the base FeatureGate interface with two critical methods:

  • SetFromMap(map[string]bool): Applies user-provided configuration to override default states (implemented at lines 106-138)
  • Add(Feature, FeatureSpec): Registers new gates during initialization

The SetFromMap implementation performs strict validation: it rejects unrecognized feature names with an "unrecognized feature gate" error and prevents modification of locked gates. Once validated, values are stored in an atomic map (f.enabled) to ensure thread-safe access during concurrent operations.

Enabling Experimental Features at Runtime

frp activates feature gates during the client initialization sequence, translating static configuration into runtime policy.

Configuration-Driven Activation

When frpc (the client) starts, the entry point in cmd/frpc/sub/root.go (lines 32-35) checks the configuration structure for a FeatureGates map:

if len(cfg.FeatureGates) > 0 {
    if err := featuregate.SetFromMap(cfg.FeatureGates); err != nil {
        return err
    }
}

This code forwards the user-defined map to the global DefaultFeatureGates singleton. The SetFromMap method iterates through each entry, validates the gate exists, and updates the internal atomic state. Because this happens during startup, the system prevents late modifications that could destabilize running components.

Validation and Safety Checks

The gate system enforces immutability constraints after initialization. Once Close() is called on the feature gate, subsequent calls to Add fail, preventing plugins or dynamic extensions from registering new gates after the system has stabilized. This design ensures that the set of experimental features remains fixed throughout the process lifetime.

Guarding Code with Runtime Checks

Experimental code paths remain dormant unless explicitly enabled through the feature gate policy layer.

Validation Layer Integration

Configuration validators query the gate state before accepting experimental fields. In pkg/config/v1/validation/client.go (lines 54-56), the VirtualNet address validator demonstrates this pattern:

if c.VirtualNet.Address != "" {
    if !featuregate.Enabled(featuregate.VirtualNet) {
        return nil, fmt.Errorf("VirtualNet feature is not enabled; enable it by setting the appropriate feature gate flag")
    }
}

This check prevents users from accidentally deploying configurations that depend on unstable networking features.

Conditional Execution Logic

Any component can query the gate state through the Enabled(Feature) method. This method first checks the override map populated by SetFromMap; if no override exists, it falls back to the FeatureSpec.Default value. For GA features, this always returns true, while Alpha features return false unless explicitly toggled.

Practical Configuration Examples

Enable an Experimental Gate in Client Configuration

Define the feature gate map in your TOML, YAML, or JSON configuration file:


# client.toml

[common]
  featureGates = { VirtualNet = true }

When the client parses this configuration, the map {"VirtualNet": true} is passed to featuregate.SetFromMap, enabling the VirtualNet capability for that session.

Programmatic Gate Activation

For custom builds or testing scenarios, enable gates programmatically before starting the client:

import "github.com/fatedier/frp/pkg/policy/featuregate"

func enableExperimental() error {
    return featuregate.SetFromMap(map[string]bool{
        string(featuregate.VirtualNet): true,
    })
}

This approach overrides only the specified gate while preserving defaults for all other experimental features.

Guarding Experimental Code Paths

Implement conditional logic to execute code only when the corresponding gate is active:

if featuregate.Enabled(featuregate.VirtualNet) {
    // Initialize experimental virtual networking
    startVirtualNetwork()
}

If the gate is disabled, the system skips the experimental block and continues along the stable code path.

Summary

  • frp's feature gate system lives in pkg/policy/featuregate/feature_gate.go and provides a thread-safe API for managing experimental capabilities.
  • Features progress through Alpha, Beta, and GA stages, with Alpha gates disabled by default and requiring explicit configuration.
  • The SetFromMap method in cmd/frpc/sub/root.go applies user configuration during client startup, validating each gate against the defaultFeatures registry.
  • Runtime guards using featuregate.Enabled() prevent unstable code execution unless the corresponding gate is toggled, as demonstrated in the validation logic of pkg/config/v1/validation/client.go.
  • The system locks the gate set after initialization to prevent runtime modifications that could compromise stability.

Frequently Asked Questions

How do I enable an experimental feature in frp?

Add a featureGates map to your client configuration file (TOML, YAML, or JSON) with the feature name set to true. For example, set featureGates = { VirtualNet = true } in your client.toml. When frpc starts, it passes this map to featuregate.SetFromMap, which activates the gate for that process.

What happens if I try to enable a feature gate that doesn't exist?

The SetFromMap method returns an error with the message "unrecognized feature gate" and prevents the client from starting. This validation ensures typos or deprecated feature names don't cause undefined behavior.

What's the difference between Alpha, Beta, and GA stages in frp feature gates?

Alpha features default to disabled and must be explicitly enabled; Beta features may default to enabled depending on the specification; GA (General Availability) features are always enabled and hidden from the gate list. Only Alpha and Beta gates appear in configuration documentation.

Can I enable feature gates programmatically in custom frp builds?

Yes. Import github.com/fatedier/frp/pkg/policy/featuregate and call featuregate.SetFromMap() with a map containing the feature names and boolean values. This must be done before the configuration validation phase to ensure the gates are active when the system checks featuregate.Enabled().

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 →