fatedier/frp · error · configmgmt.ErrInvalidArgument

invalid argument: invalid visitor config: type is required

Error message

invalid argument: invalid visitor config: type is required

What it means

Returned by UpdateStoreVisitor when the decoded body yields a nil VisitorConfigurer. The controller maps the body's "type" discriminator to a concrete visitor configurer; a body with no valid type (missing, misspelled, wrong case, or unparseable payload) decodes to nil and fails this guard with 'type is required' (ErrInvalidArgument → HTTP 400).

Source

Thrown at client/config_manager.go:306

			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	log.Infof("store: created visitor %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) UpdateStoreVisitor(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
	}
	if cfg == nil {
		return nil, fmt.Errorf("%w: invalid visitor config: type is required", configmgmt.ErrInvalidArgument)
	}
	bodyName := cfg.GetBaseConfig().Name
	if bodyName != name {
		return nil, fmt.Errorf("%w: visitor name in URL must match name in body", configmgmt.ErrInvalidArgument)
	}
	if err := m.validateStoreVisitorConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.UpdateVisitor(cfg); err != nil {
			if errors.Is(err, source.ErrNotFound) {
				return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
			}
			return err
		}
		return nil
	})

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Set "type" to a valid visitor type: stcp, sudp, or xtcp (lowercase) in the JSON body.
  2. Verify Content-Type: application/json and that the payload parses as JSON.
  3. Round-trip from GET /api/store/visitors/{name} and edit from the returned shape.

Example fix

# before
curl -XPUT .../api/store/visitors/db-v -d '{"name":"db-v","serverName":"db",...}'
# 400 invalid visitor config: type is required

# after
curl -XPUT .../api/store/visitors/db-v -d '{"type":"stcp","name":"db-v","serverName":"db",...}'
Defensive patterns

Strategy: validation

Validate before calling

// Require a valid visitor type before PUT.
visitorTypes := map[string]bool{"stcp": true, "sudp": true, "xtcp": true}
if !visitorTypes[body.Type] {
    return fmt.Errorf("missing or invalid visitor type %q (want stcp/sudp/xtcp)", body.Type)
}

Try / catch

if _, err := mgr.UpdateStoreVisitor(name, cfg); err != nil {
    if errors.Is(err, configmgmt.ErrInvalidArgument) && strings.Contains(err.Error(), "type is required") {
        // decoding yielded nil configurer: set type to stcp/sudp/xtcp and resend as JSON
    }
}

Prevention

When it happens

Trigger: PUT /api/store/visitors/{name} with a body missing "type"; type set to a proxy type like "tcp" instead of a visitor type (stcp/sudp/xtcp); sending YAML or form data where JSON is expected.

Common situations: Copying a proxy definition as the base for a visitor and forgetting to change type; case errors ("STCP"); clients defaulting Content-Type incorrectly so the strict decoder rejects the body.

Related errors


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