fatedier/frp · error

unknown visitor type: %s

Error message

unknown visitor type: %s

What it means

DecodeVisitorConfigurerJSON mirrors the proxy path: it reads the envelope "type" and calls NewVisitorConfigurerByType. If the type is not a registered visitor type (stcp, sudp, xtcp are the visitors' backing types), the factory returns nil and this error names the unknown visitor type. It fires before any field-level decoding.

Source

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

		}
		configurer.GetBaseConfig().Plugin = plugin
	}
	return configurer, nil
}

func DecodeVisitorConfigurerJSON(b []byte, options DecodeOptions) (VisitorConfigurer, error) {
	if isJSONNull(b) {
		return nil, errors.New("type is required")
	}

	var env typedEnvelope
	if err := jsonx.Unmarshal(b, &env); err != nil {
		return nil, err
	}

	configurer := NewVisitorConfigurerByType(VisitorType(env.Type))
	if configurer == nil {
		return nil, fmt.Errorf("unknown visitor type: %s", env.Type)
	}
	if err := decodeJSONWithOptions(b, configurer, options); err != nil {
		return nil, fmt.Errorf("unmarshal VisitorConfig error: %v", err)
	}

	if len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {
		plugin, err := DecodeVisitorPluginOptionsJSON(env.Plugin, options)
		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

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Use a visitor-supported type: "stcp", "sudp", or "xtcp" in the visitors section.
  2. Verify the type string is lowercase and exactly spelled.
  3. Move plain tcp/udp entries to the proxies section — they cannot be visitors.

Example fix

// before
[[visitors]]
type = "tcp"

// after
[[visitors]]
type = "stcp"
Defensive patterns

Strategy: validation

Validate before calling

// Only these types have visitor configurers.
func validVisitorType(t string) error {
	switch t {
	case "stcp", "sudp", "xtcp":
		return nil
	}
	return fmt.Errorf("unknown visitor type %q; want stcp, sudp, or xtcp", t)
}

Type guard

func isKnownVisitorType(t string) bool {
	switch t {
	case "stcp", "sudp", "xtcp":
		return true
	}
	return false
}

Try / catch

if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {
	if strings.Contains(err.Error(), "unknown visitor type") {
		// fix the visitors[] entry type; tcp/udp belong in proxies[]
	}
	return err
}

Prevention

When it happens

Trigger: Calling DecodeVisitorConfigurerJSON (or DecodeClientConfigJSON over a visitors[] entry) with type set to a proxy-only type like "tcp", a misspelled value like "STCP", or a plugin name. Only visitor-capable types resolve to a VisitorConfigurer.

Common situations: Copy-pasting a proxy block into the visitors section without changing type; assuming every proxy type has a visitor counterpart; case mismatches after config migration.

Related errors


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