A directory under modules/ with a manifest.json and a class named after it extending Module, with register() and boot(). Routes, migrations, services and templates are found by convention from there.

A module is a directory under modules/ containing:

modules/example/
  manifest.json
  Example.php            the module class
  Migrations/            dated migration files
  Routes/front.php       public routes
  Routes/admin.php       admin routes
  Controllers/Front/     and Controllers/Admin/
  Services/              the actual behaviour
  Templates/front/       .cvr templates

The manifest declares what it is:

{
    "key": "example",
    "name": "Example",
    "version": "1.0.0",
    "description": "What it does",
    "author": "You",
    "requires": ["system"],
    "application": true
}

🚨 "application": true means it appears as something an operator can switch off and can claim the front page. Leave it out for something that has no pages of its own — the OpenSearch and Realtime modules are not applications.

🚨 Depend on system alone unless you truly cannot. Every first-party application does, which is what makes a Gallery-only site work. Needing another application is usually a sign the coupling belongs in a registry instead.

Sign in to say whether this helped.