Skip to content

Understanding Caddy's Module and Plugin System

This guide explains how Caddy uses Go Interfaces to implement a dynamic, compile-time module and plugin system.


1. What is a Caddy Module?

At its core, Caddy is just a controller. All actual servers, protocols, and handlers are pluggable modules. Caddy standard modules live in modules/ and are registered statically at compile time.


2. Compile-Time Registration

Every Caddy module registers itself inside its file's init() block:

func init() {
    caddy.RegisterModule(MyModule{})
}

It registers its Module ID and a constructor function:

func (MyModule) CaddyModule() caddy.ModuleInfo {
    return caddy.ModuleInfo{
        ID:  "http.handlers.my_handler",
        New: func() caddy.Module { return new(MyModule) },
    }
}


3. Interface Guards (Go Idiom)

In Go, there is no implements keyword. A struct implements an interface implicitly by declaring its methods. To ensure that a struct doesn't accidentally break its interface requirements (which would cause compiler errors elsewhere), Caddy developers use Interface Guards:

var (
    _ caddy.Provisioner     = (*MyModule)(nil)
    _ caddy.Validator       = (*MyModule)(nil)
    _ caddyfile.Unmarshaler = (*MyModule)(nil)
)
* How it works: This line attempts to assign a nil pointer of type *MyModule to variables of interface types (caddy.Provisioner, etc.). * If MyModule is missing any of the interface's required methods, the Go compiler will fail immediately at compile-time with a clear message pointing to this line.


4. The Module Lifecycle

Caddy loads and configures modules in a strict order during a configuration change:

New() ──> JSON Unmarshal ──> Provision() ──> Validate() ──> Start() / Use ──> Stop() ──> Cleanup()
  1. New(): Creates an empty instance of your module struct.
  2. JSON Unmarshal: Populates the struct fields from the configuration JSON.
  3. Provision(caddy.Context):
  4. Interface: caddy.Provisioner
  5. Purpose: Set up internal states, establish loggers, compile regex patterns, or prepare socket listeners.
  6. Validate():
  7. Interface: caddy.Validator
  8. Purpose: Validate settings (e.g. check that a required string isn't empty, check that a path exists).
  9. Start() / Use:
  10. Interface: caddy.App (for top-level servers) or specific handler interfaces (like http.Handler).
  11. Purpose: Run the code and handle incoming requests.
  12. Stop() / Cleanup():
  13. Interface: caddy.CleanerUpper
  14. Purpose: Close open database connections, shut down sockets, and release resources.