caddyserver/caddy · error
matcher module '%s' is not a Caddyfile unmarshaler
Error message
matcher module '%s' is not a Caddyfile unmarshaler
What it means
After the matcher module is found, the adapter asserts it implements caddyfile.Unmarshaler so it can be parsed from Caddyfile tokens. Every legitimate Caddyfile matcher must implement UnmarshalCaddyfile; this error means the registered module does not, which for practical purposes means a broken or incompatible plugin build rather than a user config mistake.
Source
Thrown at caddyconfig/httpcaddyfile/httptype.go:1699
// given a matcher name and the tokens following it, parse
// the tokens as a matcher module and record it
makeMatcher := func(matcherName string, tokens []caddyfile.Token) error {
// create a new dispenser from the tokens
dispenser := caddyfile.NewDispenser(tokens)
// set the matcher name (without @) in the dispenser context so
// that matcher modules can access it to use it as their name
// (e.g. regexp matchers which use the name for capture groups)
dispenser.SetContext(caddyfile.MatcherNameCtxKey, definitionName[1:])
mod, err := caddy.GetModule("http.matchers." + matcherName)
if err != nil {
return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
}
err = unm.UnmarshalCaddyfile(dispenser)
if err != nil {
return err
}
if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
}
// nolint:staticcheck
if rm, ok := unm.(caddyhttp.RequestMatcher); ok {
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
}
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
}
View on GitHub (pinned to 50e54ee279)
Solutions
- If you develop the plugin: implement UnmarshalCaddyfile(d *caddyfile.Dispenser) error on the matcher type and add the interface guard 'var _ caddyfile.Unmarshaler = (*MyMatcher)(nil)'.
- If you only use the plugin: pin/upgrade the plugin to a release compatible with your Caddy version and rebuild with xcaddy.
- Verify with 'caddy list-modules' which module is being picked up, and remove stale duplicate plugin builds.
- As a workaround, configure the matcher in JSON config instead of Caddyfile if the module is JSON-only.
Example fix
// before (plugin matcher type)
type MatchThing struct{ Value string }
// after
type MatchThing struct{ Value string }
func (m *MatchThing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next()
if !d.NextArg() {
return d.ArgErr()
}
m.Value = d.Val()
return nil
}
var _ caddyfile.Unmarshaler = (*MatchThing)(nil) Defensive patterns
Strategy: type-guard
Type guard
// in a plugin's package, fails at compile time if the matcher cannot be used from Caddyfile var ( _ caddyfile.Unmarshaler = (*MatchThing)(nil) _ caddyhttp.RequestMatcherWithError = (*MatchThing)(nil) )
Prevention
- Add interface guards to every custom matcher module.
- Rebuild plugins in lockstep with Caddy core upgrades.
- Smoke-test adapted configs ('caddy adapt') after any binary rebuild.
When it happens
Trigger: A plugin registers itself under 'http.matchers.<name>' but its New() type does not implement caddyfile.Unmarshaler; a plugin compiled against an older Caddy API where the interface differed; JSON-only modules erroneously registered under the matchers namespace.
Common situations: Mismatched plugin/Caddy versions after an upgrade (plugin not rebuilt); developing a custom matcher module and forgetting to implement UnmarshalCaddyfile; mixing stable Caddy with plugins built from master.
Related errors
- matcher module '%s' is not a request matcher
- marshaling matcher set %#v: %v
- matcher module '%s' is not a request matcher
- getting matcher module '%s': %v
- module %s (%T) is not a listener wrapper
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/402772ba9aa67e26.
Report an issue: GitHub.