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
- Make the module implement caddyhttp.RequestMatcher (Match(*http.Request) bool) or RequestMatcherWithError, and register it under http.matchers.*.
- If it is a handler, reference it in the route chain, not inside `not`.
- 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
- Register matcher modules under http.matchers.* only.
- Add interface guards in plugins to catch API drift at build time.
- Rebuild plugins when upgrading Caddy major versions.
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
- matcher module '%s' is not a request matcher
- marshaling %T matcher: %v
- source module is not a CA pool provider
- field %s does not exist in %#v
- matcher module '%s' is not a Caddyfile unmarshaler
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/8ca09ead20a15b06.
Report an issue: GitHub.