caddyserver/caddy
Module system
Almost every behavior in Caddy is a module. The module system lives in modules.go (~12 KB), with help from context.go for runtime loading.
Purpose
Provide a uniform way to:
- Register types at package init time.
- Identify them by hierarchical, dot-separated IDs.
- Instantiate them on demand from JSON config.
- Hand them lifecycle hooks (
Provision,Validate,Cleanup).
Caddy's design says: if a piece of behavior might ever vary, make it a module instead of a flag.
Directory layout
| Path | Role |
|---|---|
modules.go |
Module / ModuleInfo / ModuleID, RegisterModule, GetModule, GetModules |
context.go |
Context.LoadModule, LoadModuleByID, LoadModules (the runtime side) |
caddy.go |
App interface, Provisioner/Validator/CleanerUpper |
modules/standard/imports.go |
The default standard build's module side-effect imports |
modules/caddyhttp/standard/imports.go |
HTTP-specific imports |
Key abstractions
| Type | File | Description |
|---|---|---|
Module |
modules.go |
Tagging interface; just CaddyModule() ModuleInfo |
ModuleInfo |
modules.go |
ID + New() constructor; returned by CaddyModule() |
ModuleID |
modules.go |
Dotted string with helpers Namespace(), Name() |
ModuleMap |
modules.go |
map[string]json.RawMessage — module-name keyed config |
Provisioner / Validator / CleanerUpper |
caddy.go |
Optional lifecycle interfaces |
App |
caddy.go |
Top-level module with Start/Stop |
How it works
graph TD
Init["init() {<br/> caddy.RegisterModule(MyMod{})<br/>}"] -->|registers| Registry[modules global map]
Config[json config field<br/>tagged with namespace] -->|"caddy:namespace=x.y"| LoadModule[Context.LoadModule]
LoadModule -->|reads inline_key or map key| Lookup
Lookup -->|finds in| Registry
Registry -->|ModuleInfo.New| Inst[new module instance]
Inst -->|json.Unmarshal| Inst
Inst -->|Provision| Inst
Inst -->|Validate| Inst
Inst -->|return as any| CallerRegistration
Every module package has an init():
func init() {
caddy.RegisterModule(MyHandler{})
}RegisterModule panics on programmer mistakes (missing ID, ID conflict with reserved names like caddy/admin, missing New). The registration store is a single mutex-guarded map (modulesMu/modules in modules.go).
Module IDs
http
http.handlers.file_server
http.matchers.path
caddy.logging.encoders.json
caddy.storage.file_system
tls.issuance.acme
dns.providers.cloudflare (from a plugin)Namespace() returns everything before the last dot; Name() returns the last label. The empty namespace is reserved for apps.
Loading from JSON
The host module declares a json.RawMessage field with a caddy struct tag:
type App struct {
HandlersRaw json.RawMessage `json:"handler,omitempty" caddy:"namespace=http.handlers inline_key=handler"`
}At provision time, the host calls ctx.LoadModule(&app, "HandlersRaw"). The context:
- Reads the namespace (
http.handlers). - Reads the inline-key field (
handler) from the JSON object to find the module name. - Looks the full ID up in the registry.
- Calls
New(), unmarshals the JSON into the new instance, runsProvision/Validate. - Returns the result as
anyfor type assertion.
For caddy:"namespace=… inline_key=…", the JSON looks like {"handler": "file_server", "root": "..."}.
For module maps, the namespace tag is sufficient and the map key supplies the module name (e.g. Config.AppsRaw is caddy:"namespace=" and the keys are app IDs like http, tls).
Lifecycle hooks
Each is optional; check for it at runtime:
if prov, ok := mod.(caddy.Provisioner); ok {
if err := prov.Provision(ctx); err != nil { ... }
}The order is: New() → json.Unmarshal → Provision() → Validate() → use → Cleanup() (when the parent context is canceled).
Standard build
Importing github.com/caddyserver/caddy/v2/modules/standard triggers all bundled init()s, which register every default module ID. Plugin builds add their own imports through xcaddy.
Integration points
- Admin API: every
/loadrequest re-runs the entire module load sequence. - Config adapters: they produce JSON; the module system consumes it.
Context:Context.LoadModule,Context.App,Context.LoadModuleByIDare the runtime API.- Logging:
Context.Logger()returns a zap logger named after the module's ID.
Entry points for modification
- A new module: add a package, register in
init(), add tomodules/standard/imports.go(or its HTTP cousin) if it should be in the default build. See Patterns and conventions. - New lifecycle hook: define a Go interface and have the host module check for it. The
RequestMatcherWithErrormigration inmodules/caddyhttp/caddyhttp.gois the canonical example. - Inspect registered modules at runtime:
caddy.GetModules(namespace)returns a sorted list. The admin API's/config/endpoint uses this kind of metadata indirectly.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.