caddyserver/caddy · error
decoded module is not a RequestMatcher or RequestMatcherWith
Error message
decoded module is not a RequestMatcher or RequestMatcherWithError: %#v
What it means
After modules decode from JSON, MatcherSets.FromInterface type-asserts each decoded value to RequestMatcher or RequestMatcherWithError. If a module loaded through the matcher namespace implements neither interface, this error is returned with the Go value printed. It signals a module registered under http.matchers.* that is not actually a matcher.
Source
Thrown at modules/caddyhttp/routes.go:453
}
}
return len(ms) == 0, nil
}
// FromInterface fills ms from an 'any' value obtained from LoadModule.
func (ms *MatcherSets) FromInterface(matcherSets any) error {
for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
var matcherSet MatcherSet
for _, matcher := range matcherSetIfaces {
if m, ok := matcher.(RequestMatcherWithError); ok {
matcherSet = append(matcherSet, m)
continue
}
if m, ok := matcher.(RequestMatcher); ok {
matcherSet = append(matcherSet, m)
continue
}
return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher)
}
*ms = append(*ms, matcherSet)
}
return nil
}
// TODO: Is this used?
func (ms MatcherSets) String() string {
var result strings.Builder
result.WriteByte('[')
for _, matcherSet := range ms {
for _, matcher := range matcherSet {
fmt.Fprintf(&result, " %#v", matcher)
}
}
result.WriteByte(']')
return result.String()
}View on GitHub (pinned to 50e54ee279)
Solutions
- If you develop the plugin: make the type implement caddyhttp.RequestMatcher (Matches(r *http.Request) bool) or RequestMatcherWithError, and add a compile-time interface guard
- Verify the module ID namespace matches the module's actual role (http.matchers.* for matchers)
- If you only consume the config: remove the invalid module from the match block and report it to the plugin author
Example fix
// before (plugin)
func (m MyThing) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{ ID: "http.matchers.mything", New: func() caddy.Module { return new(MyThing) } }
}
// MyThing has no Matches method
// after
var _ caddyhttp.RequestMatcherWithError = (*MyThing)(nil)
func (m *MyThing) Matches(r *http.Request) error { return nil } Defensive patterns
Strategy: type-guard
Type guard
// plugin-side compile-time guard (prevents shipping a matcher that isn't one):
var (
_ caddyhttp.RequestMatcherWithError = (*MyMatcher)(nil)
_ caddy.Module = (*MyMatcher)(nil)
)
// consumer-side runtime guard over decoded matchers:
func isRequestMatcher(v any) bool {
switch v.(type) {
case caddyhttp.RequestMatcher, caddyhttp.RequestMatcherWithError:
return true
}
return false
} Prevention
- Always add interface guards in plugin files
- Name custom matchers under http.matchers.* and handlers under http.handlers.*
- Test plugin configs with 'caddy validate' on an xcaddy build before shipping
When it happens
Trigger: A custom plugin module whose CaddyModule() ID is under the matchers namespace but which does not implement RequestMatcherWithError/RequestMatcher; or a module mis-decoded into the match sets field via hand-crafted JSON.
Common situations: Writing a third-party Caddy plugin and getting the module ID namespace or interface implementation wrong; very rare with stock configs.
Related errors
- unrecognized matcher name: %+v
- server block %v: %v
- matcher is defined more than once: %s
- unrecognized type for module: %s
- module '%s' has no constructor
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/103325fb8dc517ac.
Report an issue: GitHub.