Plugin permission contracts were introduced in 2.3.73 and tightened in 2.3.74. Plugins declare their permissions in plugin.json; Core validates then synchronizes it to dynamic authorization.
Namespace and route guard
Keys must be lowercase and shaped plugin.{nama-plugin}.{resource}.{action}. The provider name must be the same as the plugin name. Route dashboard can declare permission; Route permissions cannot be scoped because routes do not carry resource context. roles remains a compatibility policy and tightening filter.
{
"name": "example",
"permissions": [{
"key": "plugin.example.settings.access",
"label": "Access Example Settings",
"supports_scope": false,
"default_roles": ["admin"],
"delegable": true
}]
}
Default roles
default_roles only accept Author, Editor, or Administrator and seed the grant system role when synchronizing. If permissions keep routes and defaults unwritten, Core drops them out of compatibility roles. When both are written, the values must be identical.
Delegability
delegable:true allows custom role grants to be maintained. delegable:false limiting assignments to the default system role and synchronization removing stale grants to custom roles. This is useful for plugin operations that are not safe to delegate.
Lifecycle and file closed
Active plugins are synchronized after load and before admin_init. Disabled plugin permissions remain recognized however is_active=0; uninstall removes permissions and grants. Label/delegability changes can be updated, but semantic provider/resource/action/scope collisions cause rollback. Route permissions are not considered ready if synchronization fails.
Collision keys, ownership, Core routes, other plugin routes, or invalid contracts are rejected. Plugin ACLs are additional requirements: routes must still pass Core guard, Site Owner policy if used, compatibility role filter, and permission checks. No plugin declarations may bypass the Core guard.