What is Magento ACL?
Short answer ACL (Access Control List) in Magento 2 is the permission system that decides which admin users and API integrations can open which admin pages, system configuration sections and REST/SOAP API endpoints. Each module lists its permissions in etc/acl.xml as a tree under the root Magento_Backend::admin resource. Admin controllers check them through the ADMIN_RESOURCE constant and _isAllowed(). A role is a named set of granted resources, and both admin users and integrations are checked through Magento\Framework\AuthorizationInterface. Every admin module I write starts with this file.
- Module declares ACL resources in etc/acl.xml 01
- Admin controller checks the resource via _isAllowed() 02
- system.xml ties config sections to ACL resources 03
- webapi.xml ties REST/SOAP endpoints to ACL resources 04
- Admin role config renders the acl.xml tree as a checkbox forest 05
Most common mistake Forgetting ADMIN_RESOURCE in a custom admin controller
Five layers from acl.xml declaration to runtime permission check
ACL touches five places: the acl.xml file, a constant on each admin controller, an attribute on config sections, route bindings for REST and SOAP, and the role edit screen in the admin. Here is how they connect.
-
01
Module declares ACL resources in etc/acl.xml
Any module with admin pages or API routes ships an
etc/acl.xmlfile with a nested resource tree:<config><acl><resources><resource id="Magento_Backend::admin"><resource id="Vendor_Module::manage" title="Vendor Module" sortOrder="10"><resource id="Vendor_Module::edit" title="Edit"/></resource></resource></resources></acl></config>. Theid(for exampleVendor_Module::manage) is the permission name you reference everywhere else. Thetitleis the label an admin sees in the role edit checkbox tree. -
02
Admin controller checks the resource via _isAllowed()
An admin controller such as
Vendor\Module\Controller\Adminhtml\Manage\Indexextends\Magento\Backend\App\Actionand setsconst ADMIN_RESOURCE = 'Vendor_Module::manage';. On every request the base class calls_isAllowed(), which checks the logged-in user’s role against that resource. If the role doesn’t have it, the user gets the access denied page and the controller code never runs. If you leave outADMIN_RESOURCE, the check falls back to the root resource, and in practice any logged-in admin can open the page. I check for this first when I review a custom admin module. -
03
system.xml ties config sections to ACL resources
Each section in
etc/adminhtml/system.xmlcarries aresource="..."attribute:<section id="vendor_module" resource="Vendor_Module::manage_config">...</section>. An admin user only sees and edits thatStores → Configurationsection if their role has the matching resource ticked. I give API credentials their own section, so a role can manage the general settings without seeing the keys. -
04
webapi.xml ties REST/SOAP endpoints to ACL resources
Each REST and SOAP route in
etc/webapi.xmlnames the ACL resource it needs:<route url="/V1/vendor-module/items" method="GET"><resources><resource ref="Vendor_Module::view"/></resources></route>. An integration calling that route needs the same resource granted underSystem → Extensions → Integrations. So one acl.xml tree controls both the people in the admin and the systems calling the API. -
05
Admin role config renders the acl.xml tree as a checkbox forest
System → Permissions → User Roles → Edit Role → Role Resourcesshows the merged acl.xml tree from every module as a nested checkbox list. You tick what the role may use, for example orders and invoices but not customers or catalog. Users are linked to roles underSystem → Permissions → All Users. Integrations get their own resource list underSystem → Extensions → Integrations, so a marketplace sync token can have a narrower set than any person on the team.
Four scenarios where ACL discipline is non-negotiable
These are the four situations where I see weak ACL setup turn into audit findings, risky integrations or admin users who can do far more than their job needs.
-
Building a custom admin module
Every admin controller, config section and REST route in a custom module needs an ACL check. Without one, anyone with an admin login can reach it whatever their role. My minimum for each module: one
etc/acl.xmltree, anADMIN_RESOURCEconstant on each admin controller, aresource="..."attribute on eachsystem.xmlsection, and a<resource ref="..."/>on eachwebapi.xmlroute. -
Granular delegation across multi-team admin estates
Larger merchants have separate teams in the admin. Accounting needs orders and invoices but not customer data or catalog edits. Marketing needs CMS and email but not orders or refunds. The warehouse needs shipments and stock but not pricing. Roles under
System → Permissions → User Roleslet you tick the right resources per team. When a store has several admin users with different jobs, I set up one role per job instead of one sharedAdministratorsrole. -
Third-party API integrations (ERP, CRM, marketplace)
Each outside system that talks to Magento (ERP, CRM, marketplace connector, shipping label tool, email platform) should get its own integration under
System → Extensions → Integrations. The API tab on that form is where you limit the token to the resources it needs. A marketplace sync token should never get theMagento_Backend::adminroot. It getsMagento_Catalog::productsandMagento_Sales::sales_orderand nothing else. -
Compliance audits (PCI, SOC 2, ISO 27001)
Every formal compliance audit, PCI DSS, SOC 2 Type II, ISO 27001, HIPAA where applicable, checks that admin users operate under least-privilege access. ACL is the implementation surface the auditor will inspect: role definitions, per-role resource lists, named user-to-role mappings, and an evidence trail showing that resource grants match documented job functions. A clean ACL story (10+ named roles, no shared
Administratorsaccount, no integration on root) makes the audit straightforward; a one-role-fits-all setup invites findings.
Three ACL mistakes that leak admin privileges
Every ACL-related Magento engagement I’ve been called in to audit had at least one of these three mistakes. Fix them before the next compliance review or pen-test.
-
Forgetting ADMIN_RESOURCE in a custom admin controller
Without
const ADMIN_RESOURCE = 'Vendor_Module::resource';on a controller extending\Magento\Backend\App\Action, Magento falls back to a permissive default and every admin user with login access can hit the controller regardless of role. This is the single most common privilege-escalation bug in custom admin modules. Always setADMIN_RESOURCEto the most specific resource the action should require,Vendor_Module::editfor an edit action,Vendor_Module::deletefor a delete, and so on. Override_isAllowed()only when you need conditional logic beyond a simple resource check. -
Granting roles Magento_Backend::admin (the root resource)
Ticking the root
Magento_Backend::admincheckbox in a role-edit screen opens every child resource in the tree, the entire admin estate. Only true super-admin roles (typically one or two named individuals, plus break-glass) should ever have the root checked. Everyone else gets specific child resources matching their job function. The temptation to tick the root for a “senior” role to skip the granular work is exactly what compliance auditors flag and what attackers exploit on a compromised admin account. -
Adding ACL resources that don't appear in the role-edit tree
Magento renders only resources actually declared in a merged
etc/acl.xmlfile. If your new resource never shows up inSystem → Permissions → User Roles → Role Resources, the file is missing, the resourceidis misspelt, the parent path doesn’t exist, or you forgot tobin/magento cache:flushafter deploying. You cannot grant a resource that isn’t in the tree, controllers referencing such a resource viaADMIN_RESOURCEwill always 403. Validate by searching the merged ACL XML at runtime or by clearing the config cache and reloading the role-edit page.
Magento ACL, frequently asked questions
-
ACL vs admin role vs user, what’s the difference?
Three distinct layers. ACL is the resource tree itself, the merged set ofetc/acl.xmldeclarations from every installed module, rendered as a nested hierarchy underMagento_Backend::admin. Roles are named sets of granted resources, anAccountingrole might be ticked for orders + invoices but nothing else. Users are individual admin accounts assigned to one or more roles,jane@example.comgets theAccountingrole,bob@example.comgetsMarketing. Integrations are a parallel surface: an OAuth integration token has its own resource list, separate from any human-user role, and the resources granted there gate REST/SOAP API access viaetc/webapi.xml. -
Why doesn’t my custom admin page enforce permissions?
Three likely causes. First: the controller is missing theconst ADMIN_RESOURCE = 'Vendor_Module::resource';declaration, so Magento’s default_isAllowed()returns true for everyone. Always setADMIN_RESOURCEon every admin controller. Second: theetc/acl.xmlfile is missing, malformed, or the resourceiddoesn’t exactly match what the controller declares, case-sensitive, including theVendor_Module::prefix. Third: stale cache, ACL is config-cache-backed, so deploy +bin/magento cache:flushis required after any acl.xml change before the role-edit tree picks up the new resource. -
How do I restrict an admin user to a single store-view?
Use Role Scopes, not ACL. UnderSystem → Permissions → User Roles → Edit Role → Role Resourcesthere is aRole Scopestab that lets you tick which websites or store-views the role applies to. This is store-scope restriction, orthogonal to the resource tree. Role Scopes complements ACL but does not replace it; you still need to tick the right resources on theRole Resourcestab to gate what the role can do, and use Role Scopes to gate which scope it applies that to. A typical multi-brand merchant runs oneBrand X Managerrole per brand, each with the same resource set but a different Role Scope. -
Are REST API integrations bound to ACL?
Yes, fully. UnderSystem → Extensions → Integrations → Add New Integrationyou create an integration, give it a name and contact email, then on theAPItab you tick the ACL resources that integration’s OAuth token is allowed to hit. The user activates the integration once, Magento mints the access token, and every REST/SOAP call carrying that token is checked against the granted resources. The resources declared onetc/webapi.xmlroutes (<resource ref="..."/>) must match a resource the integration was granted, or the API call returns 401/403. The standard rule: one integration per third-party tool, scoped to exactly the resources it needs, never toMagento_Backend::admin. -
Can a role have access to admin but not to all stores?
Yes, that’s exactly what Role Scopes is for. Role Scopes restricts a role to specific websites or store-views; it’s separate from the resource-tree ACL. A typical multi-store merchant runs oneRegion Manager, EUrole with full catalog + orders resources scoped to the EU website only, and a separateRegion Manager, USrole with the same resources scoped to the US website. The two role-holders never see each other’s store data even though their ACL resource lists are identical. The combination of Role Resources (what they can do) plus Role Scopes (where they can do it) is how Magento expresses multi-tenant admin segregation. -
What’s the difference between Magento’s ACL and Laminas Acl?
Magento uses Laminas Acl under the hood,Magento\Framework\AclextendsLaminas\Permissions\Acl\Acl, so the runtime evaluation logic (resource hierarchy, role inheritance, allow/deny rules) is the Laminas implementation. What Magento adds on top is the declarative XML wrapper: the mergedetc/acl.xmltree across every installed module becomes the resource graph, role definitions live in the database (authorization_role+authorization_ruletables) and are managed through the admin UI rather than PHP code, and theAuthorizationInterfaceservice binds it all to the logged-in admin user’s session. You almost never call Laminas Acl directly, the abstraction is\Magento\Framework\AuthorizationInterface::isAllowed('Vendor_Module::resource').
Want an ACL audit on your Magento admin?
Send your storefront URL, I will review your acl.xml declarations, role definitions, named-user-to-role mappings, integration scopes, and any custom admin controllers, then reply with a written tuning plan, fixed-price quote, and earliest start date. 24-business-hour turnaround.