Browse the documentation
Building modules
The module manifest (module.json)
Every module is described by a module.json. The fields it must declare, what each one controls, and how the manifest drives discovery and compatibility.
Every module needs a module.json in its root directory. This file is the module’s identity card: it tells the ModuleManager who the module is, which class boots it, and what it depends on. Without it, the directory is simply skipped during discovery.
Full schema
{
"name": "My Module",
"version": "1.0.0",
"namespace": "Schneespur\\Module\\MyModule",
"service_provider": "Schneespur\\Module\\MyModule\\MyModuleServiceProvider",
"description": "Short description of what this module does.",
"min_schneespur_version": "1.0.0",
"requires_permissions": [],
"default_enabled": false,
"requires": {},
"conflicts": []
}
Field reference
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable module name. Shown in admin UI. |
version | string | yes | Semantic version (e.g. 1.2.3) |
namespace | string | yes | PHP namespace for PSR-4 autoloading. Must match directory structure under src/. |
service_provider | string | yes | Fully-qualified class name of the module’s ServiceProvider |
description | string | recommended | Short description. Shown in module management UI. |
min_schneespur_version | string | recommended | Minimum compatible Schneespur/Wintertrace version |
requires_permissions | string[] | optional | Core permissions the module needs to function |
default_enabled | boolean | optional | Whether the module should be enabled after installation. Default: false |
requires | object | optional | Dependency map: {"other-module": "^1.0.0"} |
conflicts | string[] | optional | Module slugs that cannot be active simultaneously |
default_enabled is intentionally false: a freshly installed module should not become active unnoticed. It runs only after deliberate activation in the module management UI.
Example from the reference module
{
"name": "Example Module",
"version": "1.0.0",
"namespace": "Schneespur\\Module\\Example",
"service_provider": "Schneespur\\Module\\Example\\ExampleServiceProvider",
"description": "Reference module demonstrating all extension points.",
"min_schneespur_version": "1.0.0",
"requires_permissions": [],
"default_enabled": false,
"requires": {},
"conflicts": []
}
Naming conventions
- Module slug: The directory name under
modules/. Use lowercase with hyphens:my-module,telegram-notifications - Namespace:
Schneespur\Module\{PascalCaseName}— e.g.Schneespur\Module\TelegramNotifications - ServiceProvider class:
{PascalCaseName}ServiceProvider— e.g.TelegramNotificationsServiceProvider
These three are linked: the slug is the directory name, the namespace determines the autoloader path, and the ServiceProvider class is the entry point named in the manifest. If any of these diverges from the directory structure, the autoloader cannot find the class.
Localised names and descriptions
For multi-language installations, name and description can be objects instead of strings:
{
"name": {
"de": "Telegram-Benachrichtigungen",
"en": "Telegram Notifications"
},
"description": {
"de": "Sendet Einsatz-Benachrichtigungen über Telegram.",
"en": "Sends job notifications via Telegram."
}
}
The Module model’s pickLocalized() method resolves to the current app locale or falls back to English.
Version constraints in requires
| Pattern | Example | Matches |
|---|---|---|
* | "*" | Any version |
>=X.Y.Z | ">=2.0.0" | 2.0.0, 2.1.0, 3.0.0, … |
^X.Y.Z | "^1.2.0" | 1.2.0, 1.3.0, 1.99.0 (not 2.0.0) |
~X.Y.Z | "~1.2.0" | 1.2.0, 1.2.1, 1.2.99 (not 1.3.0) |
These constraints are validated by the DependencyValidator before enabling — see Module lifecycle — Dependency validation.