Skip to content

ACL Resources in a Hyvä CMS Package

Hyvä CMS uses native Magento ACL permissions under one root, Hyva_Cms::cms. Every Hyvä CMS package nests its ACL resources under it, and so should any module that adds a content type. For Magento ACL itself, see Create an access control list rule in the Adobe Commerce docs.

The Hyvä CMS ACL Tree

The Hyva_Cms::cms root, titled "Hyvä CMS", sits under Magento_Backend::admin and is declared alone in src/liveview-editor/etc/acl.xml. Each entity group lives in the module that owns the entity, such as src/magento-cms/etc/acl.xml for pages and blocks. Your module nests its group under the root from its own etc/acl.xml and sequences Hyva_CmsLiveviewEditor in etc/module.xml.

A Hyvä CMS package comes in one of two shapes:

  • Entity-shaped: things with an id, such as pages, menus or forms. One group per entity, one ACL resource per verb, named <Vendor_Module>::<entity>_<verb>, such as Hyva_MenuBuilder::menu_view.
  • Feature-shaped: capabilities, such as the Media Gallery or AI translation. A group of named ACL resources, or a single one like Hyva_CmsAiTranslations::translate.

The prefix is always your own module's name.

ACL Verbs for Hyvä CMS Entities

An entity-shaped group holds one ACL resource per verb the entity supports. Nine shared verbs, in tree order:

Verb Title Covers
view View Grids and listings, opening the editor, previews, reading version history and schedules, REST GET
save Save draft Creating and editing, saving a draft, restoring a version into the draft, import, core settings in the editor
publish Publish Publish and Publish & Close, restoring a version straight to live, REST POST and PUT
delete Delete Deleting the entity, REST DELETE
export Export Export through Import/Export and its REST routes
version_delete Delete versions Deleting a version from the version history
version_organise Organise versions Renaming, pinning and adding an emoji to a version
schedule_save Schedule and reschedule Queuing or rescheduling a publication
schedule_cancel Cancel scheduled Canceling a pending publication

The version verbs sit in a <prefix>_version_history sub-group titled "Version history", the scheduling verbs in <prefix>_scheduling, titled "Scheduling". Copy titles and sort orders from the Hyvä CMS package.

Declare only the verbs your entity supports. The resolver reads them from the ACL tree, so acl.xml is the source of truth. Without publish, save is titled "Save" and REST POST and PUT check it.

When no shared verb fits, add a package verb, like Hyva_MediaGallery::asset_upload, and document it. Never reuse a shared verb's name.

This acl.xml declares a thing entity:

app/code/Vendor/Module/etc/acl.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Hyva_Cms::cms">
                    <resource id="Vendor_Module::thing" title="Things" translate="title" sortOrder="300">
                        <resource id="Vendor_Module::thing_view" title="View" translate="title" sortOrder="10"/>
                        <resource id="Vendor_Module::thing_save" title="Save draft" translate="title" sortOrder="20"/>
                        <resource id="Vendor_Module::thing_publish" title="Publish" translate="title" sortOrder="30"/>
                        <!-- delete and export follow the same pattern, then the sub-groups -->
                        <resource id="Vendor_Module::thing_version_history" title="Version history" translate="title" sortOrder="60">
                            <resource id="Vendor_Module::thing_version_delete" title="Delete versions" translate="title" sortOrder="10"/>
                            <resource id="Vendor_Module::thing_version_organise" title="Organise versions" translate="title" sortOrder="20"/>
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

For a complete one, see src/magento-cms/etc/acl.xml in the Hyvä CMS package.

Hyvä packages use sortOrder bands under the root: 10 to 80 (CMS entities and Import/Export), 100 to 135 (Menus, Forms, Blogs, Posts, Email and Newsletter templates) and 200 to 225 (Media Gallery, Dashboard, AI generation, AI translation). Pick a value outside them.

The Content Type Authorization Resolver

Shared surfaces, such as the editor, version history, scheduling and Import/Export, serve every content type. They ask Hyva\CmsLiveviewEditor\Model\Security\ContentTypeAuthorization whether the admin user may apply a verb to the request's content type.

Register your content type token, the one you give the ProviderPool, in the global etc/di.xml. Not etc/adminhtml/di.xml, or REST and SOAP never see it:

app/code/Vendor/Module/etc/di.xml
<type name="Hyva\CmsLiveviewEditor\Model\Security\ContentTypeAuthorization">
    <arguments>
        <argument name="resources" xsi:type="array">
            <item name="thing" xsi:type="string">Vendor_Module::thing</item>
        </argument>
        <argument name="labels" xsi:type="array">
            <item name="thing" xsi:type="string">thing</item>
        </argument>
    </arguments>
</type>

resources maps the token to a prefix and the resolver appends _<verb>, so thing and publish resolve to Vendor_Module::thing_publish. labels names the type in denial messages. The shared surfaces then enforce your ACL resources with no more code.

In your own code, call isAllowed() or assertAllowed() with a content type and a VERB_* class constant, getWriteVerb() to pick publish or save, and getAllowedVerbs() for one boolean per shared verb. A verb the content type doesn't declare is always denied.

An unregistered content type falls back to Magento_Backend::content for every verb and logs a warning to var/log/hyva_cms.log. That keeps older modules working. Don't rely on it.

Where Each Hyvä CMS Surface Is Gated

Surfaces scoped to one entity type use the verb's ACL resource directly, and view for listings and menu.xml. Surfaces serving several types use the root plus the resolver:

  • An admin controller that learns the content type from the request sets ADMIN_RESOURCE to Hyva_Cms::cms, then calls assertAllowed() before acting. A mass action checks every item first and refuses the whole action if one fails.
  • A Connect action in the editor checks the resolver, one verb per action.
  • A REST route that carries the content type in its payload references Hyva_Cms::cms in webapi.xml. A plugin registered in etc/webapi_rest/di.xml and etc/webapi_soap/di.xml does the per-item check, not the repository, because cron, observers and queue consumers call the repository with no admin session.

Hiding Editor Controls with Capability Flags

The Liveview editor hides controls the admin user can't use, from a permissions object built by getAllowedVerbs(). In a template, use the EditorPermissions view model, whose can() also takes package verbs:

/** @var EditorPermissions $permissions */
$permissions = $viewModels->require(EditorPermissions::class);
$canPublish = $permissions->can('publish', $contentType);

A header menu option declared in layout XML can name its verb in an acl_verb argument, and the editor drops it when can() returns false.

Hiding is a courtesy, not the gate

A hidden button doesn't stop a request. The controller, the REST route or the resolver refuses the action, so every action still needs its server-side check.

Upgrading Existing Admin Roles

Nothing migrates. Grant the new ACL resources by hand to any custom role or REST integration that reached Hyvä CMS through Magento_Backend::content. The Administrators role holds Magento_Backend::all, so it's unaffected.

ACL Checklist for Hyvä CMS Modules

Pick the list that matches what you're adding.

New admin controller

  • Declare a literal ADMIN_RESOURCE: the verb's ACL resource, or Hyva_Cms::cms plus a resolver check if the controller serves several content types.
  • On a mass action, check every item before acting on any.

New REST route

  • Reference the verb's ACL resource in webapi.xml: GET view, POST and PUT publish (or save), DELETE delete.
  • For a route carrying the content type in its payload, reference the root and check per item in a webapi_rest and webapi_soap plugin.

New content type

  • Declare the group and one ACL resource per supported verb under Hyva_Cms::cms.
  • Register the token in resources and labels in the global etc/di.xml.
  • Gate editor controls with EditorPermissions::can().

New package

  • Add an etc/acl.xml nested under Hyva_Cms::cms, with a sort order outside the Hyvä bands.
  • Sequence Hyva_CmsLiveviewEditor in etc/module.xml.
  • Choose entity-shaped or feature-shaped, and document any package verb.
  • Give a settings section its own resource under Magento_Config::config.
  • Never gate anything on Magento_Backend::content, and never ship a data patch that grants your ACL resources to existing roles.