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

  1. Include exactly one block matching the type: "stcp", "sudp", or "xtcp" object.
  2. When changing the visitor type, send a fully replaced definition with only the new block.
  3. 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

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


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