caddyserver/caddy · error

module is not a request matcher: %T

Error message

module is not a request matcher: %T

What it means

After loading the modules nested under a `not` matcher, MatchNot.Provision type-asserts each one to RequestMatcherWithError or RequestMatcher. A module that implements neither is rejected with its Go type in the message — the `not` machinery can only negate actual request matchers.

Source

Thrown at modules/caddyhttp/matchers.go:1535

// Provision loads the matcher modules to be negated.
func (m *MatchNot) Provision(ctx caddy.Context) error {
	matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
	if err != nil {
		return fmt.Errorf("loading matcher sets: %v", err)
	}
	for _, modMap := range matcherSets.([]map[string]any) {
		var ms MatcherSet
		for _, modIface := range modMap {
			if mod, ok := modIface.(RequestMatcherWithError); ok {
				ms = append(ms, mod)
				continue
			}
			if mod, ok := modIface.(RequestMatcher); ok {
				ms = append(ms, mod)
				continue
			}
			return fmt.Errorf("module is not a request matcher: %T", modIface)
		}
		m.MatcherSets = append(m.MatcherSets, ms)
	}
	return nil
}

// Match returns true if r matches m. Since this matcher negates
// the embedded matchers, false is returned if any of its matcher
// sets return true.
func (m MatchNot) Match(r *http.Request) bool {
	match, _ := m.MatchWithError(r)
	return match
}

// MatchWithError returns true if r matches m. Since this matcher
// negates the embedded matchers, false is returned if any of its
// matcher sets return true.
func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Make the module implement caddyhttp.RequestMatcher (Match(*http.Request) bool) or RequestMatcherWithError, and register it under http.matchers.*.
  2. If it is a handler, reference it in the route chain, not inside `not`.
  3. Verify interface compliance at compile time: var _ caddyhttp.RequestMatcher = (*MyMatcher)(nil).

Example fix

// before (plugin)
type Widget struct{ /* no Match method */ }

// after
var _ caddyhttp.RequestMatcherWithError = (*Widget)(nil)

func (w *Widget) MatchWithError(r *http.Request) (bool, error) { return true, nil }
Defensive patterns

Strategy: type-guard

Type guard

// Compile-time proof a module can be negated inside `not`
var _ caddyhttp.RequestMatcherWithError = (*MyMatcher)(nil)

Prevention

When it happens

Trigger: A custom module registered in a matcher namespace (e.g. http.matchers.foo) whose type implements only caddy.Provisioner or is a handler; placing a non-matcher module inside a `not` block in JSON config.

Common situations: Plugin authors putting their module in the wrong namespace; forks where a matcher type changed and lost its Match method; JSON hand-edits that put an interceptor/handler module into a matcher set.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/8ca09ead20a15b06. Report an issue: GitHub.