caddyserver/caddy · error
marshaling %T matcher: %v
Error message
marshaling %T matcher: %v
What it means
After successfully parsing Caddyfile matcher definitions, parseMatcherDefinitions marshals each matcher back to JSON so the adapted config contains it. If json.Marshal fails on the matcher value — typically custom types with unmarshalable fields (func, chan, complex, or cycles) or a broken MarshalJSON — this error reports the Go type and the marshal error.
Source
Thrown at modules/caddyhttp/matchers.go:1730
if rm, ok := unm.(RequestMatcherWithError); ok {
matcherMap[matcherName] = rm
continue
}
if rm, ok := unm.(RequestMatcher); ok {
matcherMap[matcherName] = rm
continue
}
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
}
// we should now have a functional matcher, but we also
// need to be able to marshal as JSON, otherwise config
// adaptation will be missing the matchers!
matcherSet := make(caddy.ModuleMap)
for name, matcher := range matcherMap {
jsonBytes, err := json.Marshal(matcher)
if err != nil {
return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
}
matcherSet[name] = jsonBytes
}
return matcherSet, nil
}
var wordRE = regexp.MustCompile(`\w+`)
const regexpPlaceholderPrefix = "http.regexp"
// MatcherErrorVarKey is the key used for the variable that
// holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling itView on GitHub (pinned to 50e54ee279)
Solutions
- In the plugin, implement json.Marshaler (MarshalJSON) that emits the original config form, not runtime state.
- Keep runtime-only fields unexported and ensure the rest are plain JSON-serializable types.
- Update the plugin to a version compatible with your Caddy release.
Example fix
// before (plugin)
type Fancy struct {
Re *regexp.Regexp `json:"-"`
}
// after
type Fancy struct {
Pattern string `json:"pattern"`
Re *regexp.Regexp `json:"-"`
}
func (f Fancy) MarshalJSON() ([]byte, error) { return json.Marshal(f.Pattern) } Defensive patterns
Strategy: try-catch
Try / catch
// Plugin authors: make marshaling deterministic and error-free
func (m MyMatcher) MarshalJSON() ([]byte, error) {
type plain MyMatcher // drop methods, keep fields
return json.Marshal(plain(m))
} Prevention
- Never store funcs/channels/locks in exported matcher fields.
- Implement MarshalJSON emitting the config form, not runtime state.
- Unit-test round-trip: Unmarshal(marshal(m)) in the plugin.
When it happens
Trigger: A third-party matcher holding a func/chan field or an unexported state that its MarshalJSON mishandles; a matcher type whose MarshalJSON returns an error for certain configs.
Common situations: Plugin development where the struct is provisioned into a runtime form (compiled regex, locks) with no custom marshaling back; cyclic references in matcher structs; plugin version drift after Caddy changed expected JSON shapes.
Related errors
- encoding new config: %v
- marshaling matcher set %#v: %v
- module is not a request matcher: %T
- matcher module '%s' is not a request matcher
- missing 'req' argument
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b62fb7558e7fd85a.
Report an issue: GitHub.