Core Concepts
Manifest Schema
manifest.json is the local identity and requested-capability declaration for an extension package. The loader parses it before registering actions, MIC resolvers, mail transports, admin routes, or migrations.
The recommended complete shape is:
{
"vendor": "my_vendor",
"name": "My Vendor Extension",
"version": "0.1.0",
"requiredScopes": ["content.read"],
"hasMigrations": false,
"adminRoutes": []
}
Human Path
Place the manifest at:
app/extensions/{vendor}/manifest.json
Use the same snake_case vendor in the directory and manifest. If the directory contains hyphens, the loader normalizes them to underscores before comparing against the manifest vendor. The reference directory myron-sample therefore maps to manifest vendor myron_sample.
AI Path
An AI coding agent may create and edit this file in the repository. It should prefer the complete manifest shape above even when the parser currently supplies defaults for some fields.
An installed MCP client does not write manifests. It can only interact with the runtime contributions that load after manifest parsing, registration, enablement, scope grants, and bootstrap registration succeed.
Field Reference
| Field | Type | Recommended | Current parser behavior | Failure states |
|---|---|---|---|---|
vendor | string | Required | Trimmed string; must match ^[a-z][a-z0-9_]*$ and equal the normalized directory vendor. | Missing/empty/invalid vendor; vendor-directory mismatch. |
name | string | Required | Trimmed string; must not be empty. | Missing or empty name. |
version | string | Required | Trimmed string; must not be empty. | Missing or empty version. |
requiredScopes | list<string> | Required in the recommended manifest shape | Defaults to [] when absent; must be an array when present; values must be in MyronApiScopes::ALL_BASE. | Non-list value; unknown scope. |
hasMigrations | boolean | Required in the recommended manifest shape | Defaults to false when absent; coerced to boolean. | No hard failure for absence. |
adminRoutes | list<object> | Required in the recommended manifest shape | Defaults to [] when absent; must be an array when present; entries are normalized to key and path. | Non-list value; non-object entry; invalid key; missing/absolute/traversing path. |
The current parser does not hard-fail when requiredScopes, hasMigrations, or adminRoutes are omitted. The documentation still recommends including them so package intent is explicit and reviewable.
Scope Values
Manifest scopes are limited to the six base extension scopes:
| Scope | Meaning |
|---|---|
design.read | Read design surfaces. |
design.write | Mutate design surfaces. |
content.read | Read content surfaces. |
content.write | Mutate content surfaces. |
administration.read | Read administration surfaces. |
administration.write | Mutate administration surfaces. |
The manifest declares what the extension needs. The operator grant determines what the extension is allowed to load with.
Admin Route Entries
Each admin route entry uses:
{
"key": "sample-hello",
"path": "admin/routes/hello.php"
}
Route keys must match:
^[a-z][a-z0-9_-]*$
Route paths must be relative, non-empty, not absolute, and must not contain ...
Failure Modes
| Failure | Source enforcement |
|---|---|
| Manifest file missing | ExtensionManifest::fromFile() |
| Invalid JSON | json_decode() result check |
| Invalid vendor format | ExtensionManifest::fromFile() |
| Vendor mismatch | ExtensionManifest::fromFile() |
| Missing name | ExtensionManifest::fromFile() |
| Missing version | ExtensionManifest::fromFile() |
requiredScopes is not a list | normalizeScopes() |
| Unknown scope | normalizeScopes() |
adminRoutes is not a list | normalizeAdminRoutes() |
| Admin route entry is not an object | normalizeAdminRoutes() |
| Invalid route key | normalizeAdminRoutes() |
| Unsafe route path | normalizeAdminRoutes() |
Verification
Run:
php app/tests/verify-extension-system-foundation.php
The test confirms the sample manifest parses, vendor mismatches are rejected, and unknown manifest scopes are rejected.