MyronCMS Developers Hub Docs sandbox review

Cookbook

Manifest Examples

Use these examples when creating or reviewing a local extension manifest. The parser contract is MyronExtensionManifest::fromFile().

Human Path

Create a manifest at:

app/extensions/{vendor}/manifest.json

Use a snake_case vendor that matches the normalized directory name. A directory such as myron-sample is normalized to myron_sample.

AI Path

An AI coding agent can write this file in the repository, compare it to the parser rules, and run the extension foundation verification.

An installed MCP client cannot create or edit manifests. It can only call or discover runtime contributions after an operator registers, enables, grants, and loads the extension.

Valid Complete Manifest

{
  "vendor": "my_vendor",
  "name": "My Vendor Extension",
  "version": "0.1.0",
  "requiredScopes": ["content.read"],
  "hasMigrations": false,
  "adminRoutes": []
}

This is the recommended shape even though the current parser supplies defaults for some optional fields. Explicit fields make package intent reviewable.

Reference Sample Manifest

The sample extension uses:

{
  "vendor": "myron_sample",
  "name": "Myron Sample Extension",
  "version": "0.1.0",
  "requiredScopes": ["content.read"],
  "hasMigrations": true,
  "adminRoutes": [
    {
      "key": "sample-hello",
      "path": "admin/routes/hello.php"
    }
  ]
}

The directory is app/extensions/myron-sample/; the loader normalizes that directory name to myron_sample.

Parser Defaults

ExtensionManifest.php currently behaves this way:

FieldRecommendedParser behavior when absent
vendorRequiredRejected because the empty value is not valid snake_case.
nameRequiredRejected because name is empty.
versionRequiredRejected because version is empty.
requiredScopesInclude as a listDefaults to [].
hasMigrationsInclude as a booleanDefaults to false.
adminRoutesInclude as a listDefaults to [].

Prefer the complete shape for local authoring. Defaults are parser tolerance, not a documentation style.

Invalid Examples

Invalid Vendor Format

{
  "vendor": "MyVendor",
  "name": "Bad Vendor",
  "version": "0.1.0",
  "requiredScopes": [],
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension vendor must be snake_case.

Safe fix: use lowercase snake_case, such as my_vendor.

Vendor Directory Mismatch

Directory:

app/extensions/wrong_dir/manifest.json

Manifest:

{
  "vendor": "right_vendor",
  "name": "Mismatch",
  "version": "0.1.0",
  "requiredScopes": [],
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension vendor must match its directory name.

Safe fix: rename the directory or manifest vendor so the normalized values match.

Unknown Scope

{
  "vendor": "my_vendor",
  "name": "Unknown Scope",
  "version": "0.1.0",
  "requiredScopes": ["content.publish"],
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension manifest declares unknown scope: content.publish.

Safe fix: use one of the base extension scopes documented below. Do not invent sub-scopes in a manifest.

Non-List Scopes

{
  "vendor": "my_vendor",
  "name": "Bad Scopes",
  "version": "0.1.0",
  "requiredScopes": "content.read",
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension requiredScopes must be a list.

Safe fix: use ["content.read"].

Invalid Route Key

{
  "vendor": "my_vendor",
  "name": "Bad Route Key",
  "version": "0.1.0",
  "requiredScopes": ["administration.read"],
  "hasMigrations": false,
  "adminRoutes": [
    {
      "key": "Settings",
      "path": "admin/routes/settings.php"
    }
  ]
}

Expected rejection: Extension admin route keys must be kebab-case.

Safe fix: use a lowercase key such as settings or vendor-settings.

Absolute Or Traversing Route Path

{
  "vendor": "my_vendor",
  "name": "Bad Route Path",
  "version": "0.1.0",
  "requiredScopes": ["administration.read"],
  "hasMigrations": false,
  "adminRoutes": [
    {
      "key": "settings",
      "path": "../settings.php"
    }
  ]
}

Expected rejection: Extension admin route paths must be relative paths.

Safe fix: keep paths inside the extension directory, such as admin/routes/settings.php.

Missing Name

{
  "vendor": "my_vendor",
  "version": "0.1.0",
  "requiredScopes": [],
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension name is required.

Safe fix: add a non-empty human-readable name.

Missing Version

{
  "vendor": "my_vendor",
  "name": "Missing Version",
  "requiredScopes": [],
  "hasMigrations": false,
  "adminRoutes": []
}

Expected rejection: Extension version is required.

Safe fix: add a non-empty version string.

Scopes

Manifest requiredScopes can use only these base extension scopes:

ScopeUse
design.readRead design surfaces.
design.writeMutate design surfaces.
content.readRead content surfaces.
content.writeMutate content surfaces.
administration.readRead administration surfaces.
administration.writeMutate administration surfaces.

The manifest does not grant these scopes. It declares the scopes the extension needs. The operator grant row decides whether the extension is allowed to load with those scopes.

Artifacts

Primary artifact:

  • app/extensions/{vendor}/manifest.json

Related runtime/admin artifacts:

  • /extensions/register
  • /extensions
  • extension_scope_grants
  • extension_load_errors

Safety Boundary

Do not edit the grant database directly to make a manifest load. Use the Extensions admin workflow. Do not use route paths outside the extension package. Do not add unrecognized scope strings just because an action needs a narrower policy; manifest support for new scopes requires a new evaluated contract.

Verification

Run:

php app/tests/verify-extension-system-foundation.php

That test verifies sample manifest parsing, vendor mismatch rejection, unknown scope rejection, extension grant behavior, load errors, and sample extension dispatch.

Failure Modes

FailureLikely causeSafe remediation
Extension manifest not found.File is missing or path is wrong.Create manifest.json at the extension root.
Extension manifest is not valid JSON.JSON syntax is invalid or does not decode to an object.Validate JSON and keep the root shape as an object.
Extension vendor must be snake_case.Vendor has uppercase letters, hyphens, spaces, or starts with a digit.Use ^[a-z][a-z0-9_]*$.
Extension vendor must match its directory name.Manifest vendor differs from normalized directory.Align directory and vendor.
Extension name is required.name is missing or empty.Add a non-empty name.
Extension version is required.version is missing or empty.Add a non-empty version.
Extension requiredScopes must be a list.requiredScopes is not an array.Use a JSON array.
Extension manifest declares unknown scope: ...Scope is not one of the base extension scopes.Use a supported base scope or evaluate a new scope.
Extension adminRoutes must be a list.adminRoutes is not an array.Use [] or a list of route objects.
Extension adminRoutes entries must be objects.Route entry is a string or other scalar.Use objects with key and path.
Extension admin route keys must be kebab-case.Route key format is invalid.Use lowercase letters, digits, underscore, or hyphen, starting with a letter.
Extension admin route paths must be relative paths.Path is empty, absolute, or contains ...Use an extension-local relative path.