Chat on WhatsApp
Magento glossary

What is Magento ACL ?

ACL (Access Control List) in Magento 2 is the role-based-permission system that gates admin user access to admin features, system configuration sections, and REST/SOAP API endpoints. Each module declares its permission resources in etc/acl.xml as a nested tree under the root Magento_Backend::admin resource; admin controllers check them via _isAllowed() and the ADMIN_RESOURCE constant. Admin roles are named sets of granted resources, and users plus integrations both check ACL via Magento\Framework\AuthorizationInterface.

How it works

Five layers from acl.xml declaration to runtime permission check

Magento’s ACL spans declarative XML, controller-level constants, config-section attributes, REST/SOAP route bindings, and an admin role-edit UI. Here is the wiring, end to end.

  1. 01

    Module declares ACL resources in etc/acl.xml

    Every module that needs admin-side permission gating 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 dotted-key id (e.g. Vendor_Module::manage) becomes the permission identifier used everywhere else; the title attribute is the human-readable label rendered in the admin role-edit checkbox tree.

  2. 02

    Admin controller checks the resource via _isAllowed()

    A custom admin controller — e.g. Vendor\Module\Controller\Adminhtml\Manage\Index — extends \Magento\Backend\App\Action and overrides const ADMIN_RESOURCE = 'Vendor_Module::manage';. Magento’s BackendAction base class calls _isAllowed() on every request, which checks the logged-in admin user’s role against the declared ACL resource. If the role does not have the resource ticked, Magento returns a 403 denied response and the controller body never executes. No _isAllowed() override + no ADMIN_RESOURCE means the controller is effectively wide-open to any logged-in admin.

  3. 03

    system.xml ties config sections to ACL resources

    Admin configuration sections defined in etc/adminhtml/system.xml declare a resource="..." attribute: <section id="vendor_module" resource="Vendor_Module::manage_config">...</section>. That means the admin user can only see and edit that Stores → Configuration section if their role has the matching ACL resource ticked. The same pattern works at the group level for finer-grained gating — an accounting role might see the section but only the “tax rates” group, not the “API credentials” group.

  4. 04

    webapi.xml ties REST/SOAP endpoints to ACL resources

    Every REST and SOAP endpoint declared in etc/webapi.xml binds to an ACL resource: <route url="/V1/vendor-module/items" method="GET"><resources><resource ref="Vendor_Module::view"/></resources></route>. The OAuth integration calling that endpoint needs the matching ACL resource granted under System → Extensions → Integrations at activation time. This is the same authorization surface as the admin UI — one acl.xml tree gates both human admins and third-party integrations.

  5. 05

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

    System → Permissions → User Roles → Edit Role → Role Resources renders the merged acl.xml tree (every module’s resources combined) as a nested checkbox list. The admin ticks the resources granted to that role — e.g. orders + invoices but NOT customers or catalog. Users are then assigned to roles under System → Permissions → All Users. Integrations have a parallel ACL config under System → Extensions → Integrations, separate from human-user roles, so a marketplace-sync OAuth token can be locked to a different resource set than any human admin.

When to use

Four scenarios where ACL discipline is non-negotiable

ACL is not optional plumbing on a serious Magento store. These four scenarios are where missing or sloppy ACL discipline turns into compliance findings, integration risk, or outright privilege escalation.

  • Building a custom admin module

    Every controller, every config section, every REST endpoint in a custom admin module needs ACL gating — without it, anyone with admin login sees everything regardless of role. The minimum surface: one etc/acl.xml tree, one ADMIN_RESOURCE constant per admin controller, a resource="..." attribute on every system.xml section, and a <resource ref="..."/> on every webapi.xml route. Skip any one and you have leaked a privilege escalation surface that role-based-access reviewers will flag.

  • Granular delegation across multi-team admin estates

    Larger merchants run separate admin teams — accounting needs orders + invoices but NOT customer PII or catalog edits; marketing needs CMS + email but NOT orders or refunds; warehouse needs shipments + stock but NOT pricing. Roles in System → Permissions → User Roles let you tick exactly the right resources per team. If your store has more than three admin users with different job functions, you should already be on at least three distinct ACL roles, not one shared Administrators role.

  • Third-party API integrations (ERP, CRM, marketplace)

    Every third-party integration that talks to Magento — ERP push, CRM sync, marketplace connector, shipping-label printer, marketing-automation tool — gets its own integration credentials under System → Extensions → Integrations. The ACL tab on that integration form is where you lock the OAuth token to the exact resources the integration needs. A marketplace-sync token should never get Magento_Backend::admin root; it gets Magento_Catalog::products + 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 <code>etc/acl.xml</code> declarations from every installed module, rendered as a nested hierarchy under <code>Magento_Backend::admin</code>. Roles are named sets of granted resources — an <code>Accounting</code> role might be ticked for orders + invoices but nothing else. Users are individual admin accounts assigned to one or more roles — <code>jane@example.com</code> gets the <code>Accounting</code> role, <code>bob@example.com</code> gets <code>Marketing</code>. 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 <code>etc/webapi.xml</code>.
  • Why doesn’t my custom admin page enforce permissions?
    Three likely causes. First: the controller is missing the <code>const ADMIN_RESOURCE = 'Vendor_Module::resource';</code> declaration, so Magento’s default <code>_isAllowed()</code> returns true for everyone. Always set <code>ADMIN_RESOURCE</code> on every admin controller. Second: the <code>etc/acl.xml</code> file is missing, malformed, or the resource <code>id</code> doesn’t exactly match what the controller declares — case-sensitive, including the <code>Vendor_Module::</code> prefix. Third: stale cache — ACL is config-cache-backed, so deploy + <code>bin/magento cache:flush</code> 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 <code>System → Permissions → User Roles → Edit Role → Role Resources</code> there is a <code>Role Scopes</code> 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 <code>Role Resources</code> 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 <code>Brand X Manager</code> role per brand, each with the same resource set but a different Role Scope.
  • Are REST API integrations bound to ACL?
    Yes, fully. Under <code>System → Extensions → Integrations → Add New Integration</code> you create an integration, give it a name and contact email, then on the <code>API</code> 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 <code>etc/webapi.xml</code> routes (<code><resource ref="..."/></code>) 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 <code>Magento_Backend::admin</code>.
  • 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 <code>Region Manager — EU</code> role with full catalog + orders resources scoped to the EU website only, and a separate <code>Region Manager — US</code> 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 — <code>Magento\Framework\Acl</code> extends <code>Laminas\Permissions\Acl\Acl</code> — 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 <code>etc/acl.xml</code> tree across every installed module becomes the resource graph, role definitions live in the database (<code>authorization_role</code> + <code>authorization_rule</code> tables) and are managed through the admin UI rather than PHP code, and the <code>AuthorizationInterface</code> service binds it all to the logged-in admin user’s session. You almost never call Laminas Acl directly — the abstraction is <code>\Magento\Framework\AuthorizationInterface::isAllowed('Vendor_Module::resource')</code>.
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.