{"record":{"id":"402772ba9aa67e26","repo":"caddyserver/caddy","slug":"matcher-module-s-is-not-a-caddyfile-unmarshaler","errorCode":null,"errorMessage":"matcher module '%s' is not a Caddyfile unmarshaler","messagePattern":"matcher module '(.+?)' is not a Caddyfile unmarshaler","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/httpcaddyfile/httptype.go","lineNumber":1699,"sourceCode":"\n\t// given a matcher name and the tokens following it, parse\n\t// the tokens as a matcher module and record it\n\tmakeMatcher := func(matcherName string, tokens []caddyfile.Token) error {\n\t\t// create a new dispenser from the tokens\n\t\tdispenser := caddyfile.NewDispenser(tokens)\n\n\t\t// set the matcher name (without @) in the dispenser context so\n\t\t// that matcher modules can access it to use it as their name\n\t\t// (e.g. regexp matchers which use the name for capture groups)\n\t\tdispenser.SetContext(caddyfile.MatcherNameCtxKey, definitionName[1:])\n\n\t\tmod, err := caddy.GetModule(\"http.matchers.\" + matcherName)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"getting matcher module '%s': %v\", matcherName, err)\n\t\t}\n\t\tunm, ok := mod.New().(caddyfile.Unmarshaler)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"matcher module '%s' is not a Caddyfile unmarshaler\", matcherName)\n\t\t}\n\t\terr = unm.UnmarshalCaddyfile(dispenser)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {\n\t\t\tmatchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)\n\t\t\treturn nil\n\t\t}\n\t\t// nolint:staticcheck\n\t\tif rm, ok := unm.(caddyhttp.RequestMatcher); ok {\n\t\t\tmatchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"matcher module '%s' is not a request matcher\", matcherName)\n\t}\n","sourceCodeStart":1681,"sourceCodeEnd":1717,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/httpcaddyfile/httptype.go#L1681-L1717","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (plugin matcher type)\ntype MatchThing struct{ Value string }\n\n// after\ntype MatchThing struct{ Value string }\n\nfunc (m *MatchThing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {\n\td.Next()\n\tif !d.NextArg() {\n\t\treturn d.ArgErr()\n\t}\n\tm.Value = d.Val()\n\treturn nil\n}\n\nvar _ caddyfile.Unmarshaler = (*MatchThing)(nil)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// in a plugin's package, fails at compile time if the matcher cannot be used from Caddyfile\nvar (\n\t_ caddyfile.Unmarshaler             = (*MatchThing)(nil)\n\t_ caddyhttp.RequestMatcherWithError = (*MatchThing)(nil)\n)","tryCatchPattern":null,"preventionTips":["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."],"tags":["caddyfile","matcher","plugin","interface-mismatch"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}