Module development
An Ekklesia module is a backend capability package. It owns business behavior and data declarations; it never owns components, styles, layouts, renderers, navigation, or other UI.
Required package shape
Each immediate child of modules/ must contain:
modules/example/
├── ekklesia.module.json
├── package.json
└── src/server/index.ts
package.json must use ESM, a strict semantic version, a unique package name, and:
{
"ekklesia": {
"layer": "module"
}
}
Its version must match the module manifest version. Internal package dependencies use workspace:
ranges and supported package entrypoints.
Manifest
The canonical manifest is inert JSON so discovery can validate a module without executing it:
{
"manifestVersion": 1,
"id": "calendar",
"name": "Calendar",
"version": "0.1.0",
"compatibility": {
"platform": "^0.1.0",
"moduleSdk": "^0.1.0"
},
"dependencies": {
"modules": {
"required": {
"people": "^0.1.0"
}
},
"capabilities": {
"optional": ["media.items"]
}
},
"capabilities": ["calendar.events"],
"permissions": ["calendar.events.read"],
"storage": {
"buckets": [
{
"id": "assets",
"description": "Private event assets owned by Calendar."
}
]
},
"entrypoints": {
"server": "./src/server/index.ts"
}
}
Module, capability, and permission identifiers are lowercase and namespaced. Entrypoints must begin
with ./, stay inside the package, and exist. Unknown manifest properties are rejected, including
presentation fields such as components or renderers.
Storage bucket IDs are logical names scoped to the module. Modules may request multiple buckets. Core will later map them to deployment-specific physical S3 buckets and provide scoped access; module code must not configure SeaweedFS or other S3 clients directly.
Discovery behavior
Run:
pnpm modules:validate
Discovery checks every installed module atomically. It rejects malformed manifests, duplicate IDs or package names, version mismatches, incompatible platform or SDK ranges, missing required modules or capabilities, incompatible installed dependencies, cycles, and missing or escaping entrypoints. Missing optional dependencies are allowed. Valid modules are ordered dependency-first with stable identifier sorting.
Discovery does not load server code. Lifecycle execution, route/job registration, Drizzle schema aggregation and migrations, and physical bucket provisioning are deliberately deferred.
Start from the template
Copy templates/module to modules/<your-module-id> and follow its README. The template is part of
the workspace and is validated by the same contracts and discovery implementation as installed
modules.