Open-Source Wikis

/

Caddy

/

Systems

/

Module system

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| Caller

Registration

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:

  1. Reads the namespace (http.handlers).
  2. Reads the inline-key field (handler) from the JSON object to find the module name.
  3. Looks the full ID up in the registry.
  4. Calls New(), unmarshals the JSON into the new instance, runs Provision/Validate.
  5. Returns the result as any for 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.UnmarshalProvision()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 /load request re-runs the entire module load sequence.
  • Config adapters: they produce JSON; the module system consumes it.
  • Context: Context.LoadModule, Context.App, Context.LoadModuleByID are 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 to modules/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 RequestMatcherWithError migration in modules/caddyhttp/caddyhttp.go is 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.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Module system – Caddy wiki | Factory