fatedier/frp · error
unmarshal VisitorPluginOptions error: %v
Error message
unmarshal VisitorPluginOptions error: %v
What it means
DecodeVisitorPluginOptionsJSON recognized the visitor plugin type and constructed its options struct, but decodeJSONWithOptions failed to unmarshal the JSON body into it. The underlying error is wrapped as 'unmarshal VisitorPluginOptions error', pointing at a field-level mismatch inside the visitor plugin options object.
Source
Thrown at pkg/config/v1/decode.go:146
if isJSONNull(b) {
return TypedVisitorPluginOptions{}, nil
}
var env typedEnvelope
if err := jsonx.Unmarshal(b, &env); err != nil {
return TypedVisitorPluginOptions{}, err
}
if env.Type == "" {
return TypedVisitorPluginOptions{}, errors.New("visitor plugin type is empty")
}
v, ok := visitorPluginOptionsTypeMap[env.Type]
if !ok {
return TypedVisitorPluginOptions{}, fmt.Errorf("unknown visitor plugin type: %s", env.Type)
}
optionsStruct := reflect.New(v).Interface().(VisitorPluginOptions)
if err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {
return TypedVisitorPluginOptions{}, fmt.Errorf("unmarshal VisitorPluginOptions error: %v", err)
}
return TypedVisitorPluginOptions{
Type: env.Type,
VisitorPluginOptions: optionsStruct,
}, nil
}
func DecodeClientConfigJSON(b []byte, options DecodeOptions) (ClientConfig, error) {
type rawClientConfig struct {
ClientCommonConfig
Proxies []jsonx.RawMessage `json:"proxies,omitempty"`
Visitors []jsonx.RawMessage `json:"visitors,omitempty"`
}
raw := rawClientConfig{}
if err := decodeJSONWithOptions(b, &raw, options); err != nil {
return ClientConfig{}, err
}View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use the wrapped error to locate the exact field; fix its name or JSON type.
- Match the current visitor plugin schema for your frp version.
- Remove legacy/unknown keys when strict decoding is enabled.
Example fix
// before
{"type": "my_visitor_plugin", "bindPort": "6000"}
// after
{"type": "my_visitor_plugin", "bindPort": 6000} Defensive patterns
Strategy: try-catch
Validate before calling
// Lint common numeric fields before decoding visitor plugin options.
func lintVisitorPluginOptions(b []byte) error {
var probe map[string]any
if err := json.Unmarshal(b, &probe); err != nil {
return err
}
for _, numKey := range []string{"bindPort", "port"} {
if v, ok := probe[numKey].(string); ok {
return fmt.Errorf("%s must be a number, got string %q", numKey, v)
}
}
return nil
} Try / catch
if _, err := v1.DecodeVisitorPluginOptionsJSON(b, opts); err != nil {
if strings.Contains(err.Error(), "unmarshal VisitorPluginOptions error") {
// wrapped error names the field; correct its type or name
}
return err
} Prevention
- Match visitor plugin option schemas for your frp version.
- Keep numeric fields numeric in generated JSON.
- Remove unknown keys when strict decoding is on.
When it happens
Trigger: A visitor plugin options object containing a wrong-typed field (string vs number) or unknown fields under strict DecodeOptions, passed to DecodeVisitorPluginOptionsJSON with a valid type.
Common situations: Hand-crafted visitor plugin JSON with legacy field names; schema drift between frp versions for visitor plugin options; quoted numbers from YAML pipelines.
Related errors
- unmarshal VisitorConfig error: %v
- unmarshal visitor plugin error: %v
- unmarshal ClientPluginOptions error: %v
- unknown visitor plugin type: %s
- unmarshal ProxyConfig error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/2b55fa93bc31c290.
Report an issue: GitHub.