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
- 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").
- If the plugin options are intentionally absent, pass JSON null (or omit the section) — the function returns an empty TypedClientPluginOptions for null.
- Check for key-name typos: the field is exactly "type" (case-sensitive); "plugin" or "Type" will not be recognized.
- 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
- Always emit a top-level "type" when serializing plugin options programmatically.
- Treat missing plugin options as JSON null, never {}.
- Lint frpc TOML files for plugin blocks that lack a type.
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
- visitor plugin type is empty
- unmarshal proxy plugin error: %v
- unmarshal visitor plugin error: %v
- unknown plugin type: %s
- unmarshal ClientPluginOptions error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/9a8140c412bf0e72.
Report an issue: GitHub.