fatedier/frp · error

plugin type is empty

Error message

plugin type is empty

What it means

Thrown by DecodeClientPluginOptionsJSON in frp's config v1 package when the JSON payload for a client plugin (e.g. an [[proxies]] entry's plugin options) is not JSON null but contains no top-level "type" field (or it is an empty string). The decoder uses a typedEnvelope to route the payload to the concrete plugin options struct; without a type there is no mapping to dispatch to. It is a strict config-decoding guard that fires before unknown-type checking.

Source

Thrown at pkg/config/v1/decode.go:110

		if err != nil {
			return nil, fmt.Errorf("unmarshal visitor plugin error: %v", err)
		}
		configurer.GetBaseConfig().Plugin = plugin
	}
	return configurer, nil
}

func DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (TypedClientPluginOptions, error) {
	if isJSONNull(b) {
		return TypedClientPluginOptions{}, nil
	}

	var env typedEnvelope
	if err := jsonx.Unmarshal(b, &env); err != nil {
		return TypedClientPluginOptions{}, err
	}
	if env.Type == "" {
		return TypedClientPluginOptions{}, errors.New("plugin type is empty")
	}

	v, ok := clientPluginOptionsTypeMap[env.Type]
	if !ok {
		return TypedClientPluginOptions{}, fmt.Errorf("unknown plugin type: %s", env.Type)
	}
	optionsStruct := reflect.New(v).Interface().(ClientPluginOptions)
	if err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {
		return TypedClientPluginOptions{}, fmt.Errorf("unmarshal ClientPluginOptions error: %v", err)
	}
	return TypedClientPluginOptions{
		Type:                env.Type,
		ClientPluginOptions: optionsStruct,
	}, nil
}

func DecodeVisitorPluginOptionsJSON(b []byte, options DecodeOptions) (TypedVisitorPluginOptions, error) {
	if isJSONNull(b) {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Ensure the plugin options JSON object includes a non-empty top-level "type" field matching a registered client plugin (e.g. "http_proxy", "unix_domain_socket").
  2. If the plugin options are intentionally absent, pass JSON null (or omit the section) — the function returns an empty TypedClientPluginOptions for null.
  3. Check for key-name typos: the field is exactly "type" (case-sensitive); "plugin" or "Type" will not be recognized.
  4. If using TOML, verify the plugin type is declared via plugin = "<type>" so the loader can inject the envelope type before decoding.

Example fix

# before (frpc.toml)
[[proxies]]
name = "web"
plugin = ""
[proxies.pluginOptions]
localAddr = ":80"

# after
[[proxies]]
name = "web"
type = "tcp"
[proxies.plugin]
type = "http_proxy"
localAddr = ":80"
Defensive patterns

Strategy: validation

Validate before calling

// before calling DecodeClientPluginOptionsJSON
var env struct{ Type string `json:"type"` }
_ = jsonx.Unmarshal(b, &env)
if !isJSONNull(b) && env.Type == "" {
    return fmt.Errorf("plugin options JSON must declare a non-empty top-level \"type\" field")
}

Type guard

func hasClientPluginType(b []byte) bool {
    if isJSONNull(b) {
        return true // null is allowed
    }
    var env struct{ Type string `json:"type"` }
    if err := jsonx.Unmarshal(b, &env); err != nil {
        return false
    }
    _, ok := v1.ClientPluginOptionsTypeMap()[env.Type]
    return env.Type != ""
}

Try / catch

if _, err := v1.DecodeClientPluginOptionsJSON(raw, v1.DecodeOptions{}); err != nil {
    if strings.Contains(err.Error(), "plugin type is empty") {
        // config bug: add/repair the "type" key, or use null to omit options
    }
    return err
}

Prevention

When it happens

Trigger: Calling DecodeClientPluginOptionsJSON(b, options) where b unmarshals to a typedEnvelope with Type == "" — e.g. the JSON is {} , {"type":""}, or an object whose plugin type key is missing/misnamed (case-sensitive "type"). Passing JSON null does NOT trigger it (early return).

Common situations: Hand-writing frpc TOML where plugin = "http_proxy" is set but the plugin options are given as a bare table without type, converting YAML/JSON configs that drop the type field, or programmatic config generation that serializes plugin options from a struct while forgetting to include Type.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/9a8140c412bf0e72. Report an issue: GitHub.