Skip to content
5.0 Upwork, 100+ reviews Adobe Certified since 2021 New: become your own Magento developer Learn more →
Magento glossary

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.

How it works
  1. Module declares ACL resources in etc/acl.xml 01
  2. Admin controller checks the resource via _isAllowed() 02
  3. system.xml ties config sections to ACL resources 03
  4. webapi.xml ties REST/SOAP endpoints to ACL resources 04
  5. Admin role config renders the acl.xml tree as a checkbox forest 05
Each step is explained below

Most common mistake Forgetting ADMIN_RESOURCE in a custom admin controller

How it works

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.

  1. 01

    Module declares ACL resources in etc/acl.xml

    Any module with admin pages or API routes ships an etc/acl.xml file 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>. The id (for example Vendor_Module::manage) is the permission name you reference everywhere else. The title is the label an admin sees in the role edit checkbox tree.

  2. 02

    Admin controller checks the resource via _isAllowed()

    An admin controller such as Vendor\Module\Controller\Adminhtml\Manage\Index extends \Magento\Backend\App\Action and sets const 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 out ADMIN_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.

  3. 03

    system.xml ties config sections to ACL resources

    Each section in etc/adminhtml/system.xml carries a resource="..." attribute: <section id="vendor_module" resource="Vendor_Module::manage_config">...</section>. An admin user only sees and edits that Stores → Configuration section 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.

  4. 04

    webapi.xml ties REST/SOAP endpoints to ACL resources

    Each REST and SOAP route in etc/webapi.xml names 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 under System → Extensions → Integrations. So one acl.xml tree controls both the people in the admin and the systems calling the API.

  5. 05

    Admin role config renders the acl.xml tree as a checkbox forest

    System → Permissions → User Roles → Edit Role → Role Resources shows 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 under System → Permissions → All Users. Integrations get their own resource list under System → Extensions → Integrations, so a marketplace sync token can have a narrower set than any person on the team.

When to use

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.xml tree, an ADMIN_RESOURCE constant on each admin controller, a resource="..." attribute on each system.xml section, and a <resource ref="..."/> on each webapi.xml route.

  • 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 Roles let 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 shared Administrators role.

  • 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 the Magento_Backend::admin root. It gets Magento_Catalog::products and Magento_Sales::sales_order and 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 Administrators account, no integration on root) makes the audit straightforward; a one-role-fits-all setup invites findings.

Common mistakes

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 set ADMIN_RESOURCE to the most specific resource the action should require, Vendor_Module::edit for an edit action, Vendor_Module::delete for 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::admin checkbox 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.xml file. If your new resource never shows up in System → Permissions → User Roles → Role Resources, the file is missing, the resource id is misspelt, the parent path doesn’t exist, or you forgot to bin/magento cache:flush after deploying. You cannot grant a resource that isn’t in the tree, controllers referencing such a resource via ADMIN_RESOURCE will always 403. Validate by searching the merged ACL XML at runtime or by clearing the config cache and reloading the role-edit page.

FAQ

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 of etc/acl.xml declarations from every installed module, rendered as a nested hierarchy under Magento_Backend::admin. Roles are named sets of granted resources, an Accounting role might be ticked for orders + invoices but nothing else. Users are individual admin accounts assigned to one or more roles, jane@example.com gets the Accounting role, bob@example.com gets Marketing. 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 via etc/webapi.xml.
  • Why doesn’t my custom admin page enforce permissions?
    Three likely causes. First: the controller is missing the const ADMIN_RESOURCE = 'Vendor_Module::resource'; declaration, so Magento’s default _isAllowed() returns true for everyone. Always set ADMIN_RESOURCE on every admin controller. Second: the etc/acl.xml file is missing, malformed, or the resource id doesn’t exactly match what the controller declares, case-sensitive, including the Vendor_Module:: prefix. Third: stale cache, ACL is config-cache-backed, so deploy + bin/magento cache:flush is 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. Under System → Permissions → User Roles → Edit Role → Role Resources there is a Role Scopes tab 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 the Role Resources tab 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 one Brand X Manager role per brand, each with the same resource set but a different Role Scope.
  • Are REST API integrations bound to ACL?
    Yes, fully. Under System → Extensions → Integrations → Add New Integration you create an integration, give it a name and contact email, then on the API tab 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 on etc/webapi.xml routes (<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 to Magento_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 one Region Manager, EU role with full catalog + orders resources scoped to the EU website only, and a separate Region Manager, US role 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\Acl extends Laminas\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 merged etc/acl.xml tree across every installed module becomes the resource graph, role definitions live in the database (authorization_role + authorization_rule tables) and are managed through the admin UI rather than PHP code, and the AuthorizationInterface service 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').
ACL audit

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.

Kishan Savaliya Online 10 AM to 7 PM IST Chat on WhatsApp