fatedier/frp · error
type shouldn't be empty
Error message
type shouldn't be empty
What it means
NewVisitorConfFromIni reads the type key of a visitor INI section; unlike proxies, visitors have no default. If type is empty the config is rejected immediately with 'type shouldn't be empty'. Visitors must explicitly declare stcp, sudp, or xtcp.
Solutions
- Add type = stcp (or sudp/xtcp) to the visitor section
- If the section was not meant to be a visitor, rename it so it is not detected as one (check the naming convention used to detect visitor sections)
- Validate with frpc verify -c frpc.ini
Example fix
# before [secret-visitor] name = sv server_name = secret # after [secret-visitor] type = stcp name = sv server_name = secret
Defensive patterns
Strategy: validation
Validate before calling
if section.Key("type").String() == "" {
return fmt.Errorf("visitor section [%s] missing type (stcp/sudp/xtcp)", section.Name())
} Type guard
func hasVisitorType(s *ini.Section) bool { return s.Key("type").String() != "" } Try / catch
if _, err := legacy.NewVisitorConfFromIni(prefix, name, section); err != nil && err.Error() == "type shouldn't be empty" { /* add type key */ } Prevention
- Never copy a proxy section into a visitor section without rewriting fields
- Require explicit type in visitor snippets
- frpc verify before deploy
When it happens
Trigger: A [visitor.x] or similarly named section in legacy frpc.ini where the type key is missing or blank. Merely having a section named like a visitor without a type triggers it during LoadAllProxyConfsFromIni.
Common situations: Users adding a visitor section by copying a proxy section and deleting fields; forgetting that visitors require type while proxies silently default to tcp.
Related errors
- type [ ] error
- bind port is required
- invalid heartbeat_timeout, heartbeat_timeout is less than…
- invalid protocol
- invalid type [ ]
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/f0f675cd12cabb3b.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/config/legacy/visitor.go:173
if cfg.MaxRetriesAnHour <= 0 {
cfg.MaxRetriesAnHour = 8
}
if cfg.MinRetryInterval <= 0 {
cfg.MinRetryInterval = 90
}
if cfg.FallbackTimeoutMs <= 0 {
cfg.FallbackTimeoutMs = 1000
}
return
}
// Visitor loaded from ini
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
// section.Key: if key not exists, section will set it with default value.
visitorType := VisitorType(section.Key("type").String())
if visitorType == "" {
return nil, fmt.Errorf("type shouldn't be empty")
}
conf := DefaultVisitorConf(visitorType)
if conf == nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
return conf, nil
}
View on GitHub (pinned to 6c8a8d0a97)