fatedier/frp · error
exactly one visitor type block is required
Error message
exactly one visitor type block is required
What it means
Returned by VisitorDefinition.Validate when the count of populated visitor type blocks (stcp/sudp/xtcp) is not exactly one. Zero blocks (only name/type) or multiple blocks (e.g. both stcp and xtcp set) both fail with this error.
Source
Thrown at client/http/model/visitor_definition.go:32
STCP *v1.STCPVisitorConfig `json:"stcp,omitempty"`
SUDP *v1.SUDPVisitorConfig `json:"sudp,omitempty"`
XTCP *v1.XTCPVisitorConfig `json:"xtcp,omitempty"`
}
func (p *VisitorDefinition) Validate(pathName string, isUpdate bool) error {
if strings.TrimSpace(p.Name) == "" {
return fmt.Errorf("visitor name is required")
}
if !IsVisitorType(p.Type) {
return fmt.Errorf("invalid visitor type: %s", p.Type)
}
if isUpdate && pathName != "" && pathName != p.Name {
return fmt.Errorf("visitor name in URL must match name in body")
}
_, blockType, blockCount := p.activeBlock()
if blockCount != 1 {
return fmt.Errorf("exactly one visitor type block is required")
}
if blockType != p.Type {
return fmt.Errorf("visitor type block %q does not match type %q", blockType, p.Type)
}
return nil
}
func (p *VisitorDefinition) ToConfigurer() (v1.VisitorConfigurer, error) {
block, _, _ := p.activeBlock()
if block == nil {
return nil, fmt.Errorf("exactly one visitor type block is required")
}
cfg := block
cfg.GetBaseConfig().Name = p.Name
cfg.GetBaseConfig().Type = p.Type
return cfg, nil
}View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Include exactly one block matching the type: "stcp", "sudp", or "xtcp" object.
- When changing the visitor type, send a fully replaced definition with only the new block.
- Pre-validate the payload: exactly one of the three block keys is non-null.
Example fix
// before
{"name": "v", "type": "stcp"}
// after
{"name": "v", "type": "stcp", "stcp": {"serverName": "svc", "secretKey": "k", "bindAddr": "127.0.0.1", "bindPort": 6000}} Defensive patterns
Strategy: validation
Validate before calling
func countVisitorBlocks(def map[string]any) int {
n := 0
for _, k := range []string{"stcp","sudp","xtcp"} {
if def[k] != nil { n++ }
}
return n
}
// require countVisitorBlocks(def) == 1 Prevention
- Use a fresh struct when changing visitor type so old blocks disappear.
- Serialize with omitempty and never merge visitor payloads.
- Unit-test generated payloads for exactly one block.
When it happens
Trigger: POST/PUT /api/visitors with {"name":"v","type":"stcp"} and no "stcp" block; or a body with both "stcp" and "sudp" objects present.
Common situations: Forgetting the nested block; switching visitor type by adding a new block while leaving the old one non-null; JSON merge tools accumulating blocks.
Related errors
- visitor name is required
- invalid visitor type: %s
- visitor type block %q does not match type %q
- proxy name is required
- invalid proxy type: %s
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/73ed7555ea876a56.
Report an issue: GitHub.