Skip to main content
Joomla ACL Explained: Groups, Access Levels and Permissions
On this page

Joomla ACL Explained: Groups, Access Levels and Permissions

08 August 2026

Every click in Joomla passes an invisible checkpoint. Can this visitor see this article? May this editor change that module? Is this API request allowed to create content? The system that answers all those questions is Joomla's Access Control List, or ACL, and it is one of the most powerful features of the CMS - and one of the least understood.

This article explains how Joomla ACL really works, from the first permission dropdown to the assets tree in the database. It covers the basics for owners and editors, practical permission recipes and debugging for administrators, and the technical details for developers who want to use ACL in their own extensions. It builds on the Focus On article about Joomla users and user groups, which introduces accounts, groups, and access levels; here we go deep into the permission machinery itself.

Joomla never asks "who are you?" twice. It asks "what may you do?" on every single action.

The goal is simple: help you understand ACL well enough to design it, debug it, and trust it.

1. The Basics

1.1 Three Questions, Three Mechanisms

Joomla's access control answers three different questions with three different mechanisms. Keeping them apart is half the battle:

QuestionMechanismExample
Who is this user? User groups "This account belongs to Editors."
What may they see? View access levels "This menu item is visible to Registered."
What may they do? Permissions (actions) "Editors may edit articles in this category."

Groups are the foundation: both view levels and permissions are always granted to groups, never to individual users. If you ever think "I want to give this one person extra rights", the Joomla answer is: create a group for that role first.

1.2 Where ACL Lives in the Backend

  • Users → Groups: the group tree.
  • Users → Access Levels: who can see what.
  • System → Global Configuration → Permissions: the site-wide permission defaults.
  • Every component's Options → Permissions tab: component-level rules.
  • Every category and many items: their own Permissions tab for fine-grained rules.

1.3 Why ACL Feels Difficult

Joomla ACL is not complicated because it is badly designed. It feels complicated because it is layered: a single "may Bob edit this article?" decision can involve the global defaults, the component rules, the category rules, the article rules, and every group Bob inherits from. Once you can read those layers in order, the system becomes predictable. That reading order is exactly what this article teaches.

Back to top

2. The Building Blocks

2.1 The Default Group Tree

A fresh Joomla 6 installation ships nine groups in a tree. Children inherit everything their parents are granted:

Public (id 1)
├─ Guest (9)          visitors who are not logged in
├─ Registered (2)     logged-in users
│  └─ Author (3)      may create articles
│     └─ Editor (4)   may also edit articles of others
│        └─ Publisher (5)  may also publish and unpublish
├─ Manager (6)        backend content work
│  └─ Administrator (7)  backend management, most components
└─ Super Users (8)    everything, everywhere

Two branches matter: the content branch (Registered → Author → Editor → Publisher) works mostly on the frontend, and the management branch (Manager → Administrator) works in the backend. Super Users stand apart and answer to nobody.

Do not overestimate the Administrator group, though. On a fresh installation even Administrators cannot open the Global Configuration, cannot install extensions, and cannot fill in custom field values. The default rules in section 5.2 show exactly why, and pitfall 13.7 shows how this surprises people in practice.

2.2 View Access Levels are Just Lists of Groups

A view access level is nothing more than a list of groups. The default levels in the database show it plainly (the rules column of #__viewlevels holds group IDs):

LevelGroups inside (IDs)
Public [1] - everyone.
Registered [6,2,8] - Manager, Registered, Super Users.
Special [6,3,8] - Manager, Author, Super Users.
Guest [9] - only visitors who are not logged in.
Super Users [8].

When you assign the level "Registered" to an article, Joomla checks: is the visitor in one of these groups, or in a child of one of them? If yes, the article is visible. View levels control visibility only - they never grant the right to change anything.

2.3 Actions: the Verbs of ACL

Permissions are built from actions. The standard set, declared by com_content and most other components, reads like a list of verbs:

ActionGrants the right to
core.admin Change the component's permissions and options. The "ACL key" itself.
core.options Change the component's options, without the permissions tab.
core.manage Open the component in the backend at all.
core.create Create new items.
core.delete Delete items.
core.edit Edit any item.
core.edit.own Edit only items you created yourself.
core.edit.state Publish, unpublish, archive, trash.
core.edit.value Change the value of custom fields.
core.manage.workflow / core.execute.transition Manage and use publishing workflows (com_content).

The site itself also has login actions, stored on the root of the permission tree: core.login.site, core.login.admin, core.login.api, and core.login.offline. That is where "may this group log in to the administrator?" is decided - and why API access needs its own explicit grant.

Back to top

3. The Four Permission States

3.1 What the Dropdown Really Means

Every permission dropdown in Joomla offers a small set of states, and two of them look more alike than they are:

StateMeaning
Inherited No opinion here. Use whatever the parent (group or level above) decided.
Allowed Explicitly grant the action at this level.
Denied Explicitly forbid the action - and lock that decision for everything below.
Not Allowed (calculated) The end result when nobody ever said "Allowed". Not a choice; a default.

3.2 The Golden Rule: Deny is Final

The single most important ACL rule in Joomla: an explicit Denied can never be undone at a lower level. If a group is denied core.edit on a component, no category rule, no item rule, and no child group can switch it back to allowed. Deeper levels can only restrict further, never re-open.

This is why experienced Joomla administrators treat "Denied" like a fire axe behind glass: almost never the right tool. The default state of every action is already "Not Allowed" - if you simply never grant a permission, the user cannot do it. Use Inherited and Allowed to build permissions up; reach for Denied only when you must carve an exception out of something already allowed higher up.

3.3 Not Allowed vs Denied

"Not Allowed" and "Denied" produce the same behaviour today (the user cannot do it) but behave completely differently tomorrow. "Not Allowed" flips to allowed the moment any parent grants the action. "Denied" stays denied forever, no matter what parents grant later. When you audit a site's ACL, every unnecessary "Denied" is a future support ticket.

Back to top

4. The Permission Hierarchy

4.1 Four Levels, Evaluated Top-Down

Joomla evaluates permissions along a chain from general to specific:

Global Configuration  (the site-wide defaults, per group)
        ↓
Component             (Options > Permissions of com_content, com_media, ...)
        ↓
Category              (the Permissions tab of a category; nested categories inherit)
        ↓
Item                  (the Permissions tab of one article, one module, ...)

Each level starts from what the level above decided (that is what "Inherited" points to) and may add its own Allowed or Denied. In parallel, the group tree inherits from parent group to child group. A user's final permission is the combination of both inheritance lines: all their groups, across all levels of the hierarchy.

4.2 Where to Set What

A practical rule of thumb for keeping ACL maintainable:

  • Global Configuration: broad roles. "Managers may log in to the backend."
  • Component: what a role may do in one area. "Editors may edit articles."
  • Category: departmental boundaries. "Marketing editors may edit only the News category."
  • Item: rare exceptions. The more item-level rules you have, the harder your site is to reason about.

4.3 The Calculated Setting Column

Every permissions tab shows a Calculated Setting column next to your chosen state. That column is the truth: it shows the result after all inheritance is applied. When you change a dropdown, save first - the calculated column only updates after saving. Reading this column at the right level answers most "why can't this user…?" questions before any debugging starts.

Back to top

5. The Assets Tree

5.1 Where Permissions Are Stored

Every object that can carry permissions - the site itself, each component, each category, each article - has one row in the #__assets table. The rows form a tree, stored as a nested set (with lft and rgt columns) so Joomla can fetch a whole branch in one query:

root.1                          the site (Global Configuration rules)
├─ com_content                 the component
│  ├─ com_content.category.14  a category
│  │  └─ com_content.article.42   one article
└─ com_banners
   └─ com_banners.category.3

The asset name follows a strict pattern: {component}.{type}.{id}. When you edit permissions on an article, you are editing the rules of asset com_content.article.42. When you edit the Global Configuration permissions, you are editing root.1.

5.2 Rules are JSON

Each asset row has a rules column holding a small JSON object: action → group ID → 1 (allow) or 0 (deny). Real examples from a fresh Joomla 6 database:

com_banners:
{"core.admin":{"7":1},"core.manage":{"6":1}}
  = Administrators (7) may configure it,
    Managers (6) may manage it.

root.1 (fragment):
{"core.admin":{"8":1},
 "core.manage":{"7":1},
 "core.login.site":{"6":1,"2":1},
 "core.login.admin":{"6":1},
 "core.login.api":{"8":1}}
  = only Super Users (8) hold the master key core.admin,
    Administrators (7) may manage; Managers and Registered
    may log in to the site, Managers to the backend,
    Super Users to the API.

com_installer:
{"core.manage":{"7":0},"core.delete":{"7":0},"core.edit.state":{"7":0}}
  = an explicit 0: core Joomla itself DENIES the
    Administrators group access to the extension installer.

An empty object {} means "no opinion at this level, inherit everything" - which is exactly what most categories and items should have. If you ever wondered what the Permissions tab really saves: it is this one JSON value.

The defaults are as interesting for what they do not say. On root.1 no group is granted core.options, and no group anywhere is granted core.edit.value (the custom fields action). Because nothing grants them, they calculate to "Not Allowed (Inherited)" for every group below Super Users - Administrators included. That is why an Administrator sees Configure Options as Not Allowed, and why on a fresh site only Super Users can fill in custom field values. And com_installer is the one place where core Joomla itself swings the fire axe from section 3.2: an explicit deny that keeps Administrators out of the installer, unchangeable at any deeper level.

5.3 Why the Tree Must Stay Healthy

Because assets form a nested set, a corrupted tree (after failed migrations or direct database edits) produces baffling symptoms: permissions that apply to the wrong items, categories that ignore their component rules. The fix is to rebuild the tree - see the debugging section below.

Back to top

6. How Joomla Calculates a Permission

6.1 The Walk

When code asks "may user 99 do core.edit on com_content.article.42?", Joomla performs a fixed walk:

  1. Collect all groups of user 99, including every parent group up the tree.
  2. Load the rules of the asset and all its ancestors: article 42 → category 14 → com_content → root.1.
  3. Merge the rules top-down. For each action, a deeper level overrides "Inherited" - but a 0 (deny) is sticky: once any applicable group hits a deny, the merge result for that group stays deny.
  4. The final answer is yes only if at least one of the user's groups ends on 1 (allow) and none of them ends on 0 for that action... a single denied group vetoes the lot.

That last point deserves emphasis: a user with two groups gets the union of their allows, but any deny in any of their groups wins. Adding a user to an extra group can therefore remove abilities, if that group carries a deny. This surprises almost everyone once.

6.2 The Super User Exception

One check happens before all of this. If any of the user's groups has core.admin allowed on the root asset, the user is a Super User, and authorise() answers yes to everything without walking the tree. This is also why "Super User" is not a magic hard-coded group: any group you grant core.admin on the Global Configuration level becomes a super-user group. Grant it with great care.

There is one more back door, by design: the root_user setting in configuration.php can name one account as an emergency super user, independent of any group. Joomla's own code checks it as a failsafe. If you are ever locked out of a site, this is the official rescue hatch - and if you find it set on a site you inherit, ask why.

6.3 Guests Are Calculated Too

A visitor who is not logged in is not outside the system. They are treated as a member of the Guest group (which is why the Guest view level exists), and the same walk applies. "Public" content is simply content whose view level contains the Public group that everyone, including guests, belongs to.

Back to top

7. Real-World ACL Recipes

7.1 A Department That Manages Only Its Own Category

Goal: the Marketing team edits articles in the News category and nowhere else.

  1. Create group Marketing with parent Registered (not Editor - you will grant rights explicitly).
  2. In com_content → Options → Permissions, leave Marketing on Inherited everywhere.
  3. Open the News category → Permissions, and set for Marketing: Create = Allowed, Edit = Allowed, Edit State = Allowed.
  4. Assign the marketing users to the Marketing group.

Because the grants live on the category asset, they apply to that category and its children - and nothing else. No deny needed anywhere.

7.2 A Frontend-Only Content Manager

Goal: a client contact who can create, edit, and publish articles from the frontend, but never sees the backend.

  1. Create group Content Manager under Registered.
  2. In com_content permissions, allow Create, Edit, Edit State for the group.
  3. Do not grant core.login.admin anywhere. Without the backend login action, the backend simply does not exist for them.

7.3 A Members-Only Area

Goal: paying members see premium content; everyone else sees a teaser.

  1. Create group Members under Registered.
  2. Create view access level Premium containing Members (and, deliberately, Super Users - see the pitfalls below).
  3. Set the premium articles, modules, and menu items to level Premium.

Note that no permissions were touched at all: pure visibility problems need only groups and view levels. Reaching for the Permissions tab here would be over-engineering.

7.4 A Locked-Down API Integration User

Goal: an external system posts articles through the Web Services API with the least possible power.

  1. Create group API Writer under Registered.
  2. Global Configuration → Permissions: allow Site API Login (core.login.api) for the group.
  3. com_content permissions: allow Create (and nothing else) for the group.
  4. Create one user in only this group, enable an API token for it, and let the integration use that token.

If the token ever leaks, the attacker can create unpublished articles - and that is all.

Back to top

8. Debugging Permissions

8.1 The Built-in Permission Debugger

Few people know Joomla ships an ACL debugger. In Users → Manage, open the actions for a user and choose Debug User (there is a matching Debug Group for groups). You get a full matrix: every asset on the site against every action, showing exactly where each permission comes from - explicitly allowed, forbidden, or inherited. When a permission puzzle resists the calculated-setting column, this view is your microscope.

8.2 A Reading Order for "User X Cannot Do Y"

  1. Which groups does the user actually have? (Check the user edit form; remember parent groups count too.)
  2. Is it a visibility problem (view level) or a permission problem (action)? They fail differently: invisible vs "not authorised".
  3. Walk the hierarchy top-down: Global → component → category → item, reading the calculated column for the user's groups.
  4. Hunt for an explicit Denied in any of the user's groups, at any level. One is enough to veto everything.
  5. Still stuck? Open Debug User and read the row for the exact asset.

8.3 Looking at the Database Directly

Three queries answer most forensic questions:

-- What rules does one asset carry?
SELECT name, rules FROM #__assets
WHERE name = 'com_content.category.14';

-- Which assets carry an explicit deny (value 0)?
SELECT name, rules FROM #__assets
WHERE rules LIKE '%:0%';

-- Which groups does a user belong to?
SELECT g.title FROM #__usergroups AS g
JOIN #__user_usergroup_map AS m ON m.group_id = g.id
WHERE m.user_id = 99;

8.4 Rebuilding the Assets Tree

If permissions behave nonsensically after a migration or a crashed import, rebuild the nested set: Users → Groups → Rebuild button rebuilds the group tree, and the assets table has a matching rebuild in the database fix tools (System → Database → Update Structure repairs many asset issues). Never hand-edit lft/rgt values.

Back to top

9. Under the Hood (Developer View)

9.1 The Tables Involved

TableRole
#__assets The permission tree: one row per permissable object, rules as JSON.
#__usergroups The group tree (also a nested set).
#__user_usergroup_map Which user is in which groups (many-to-many).
#__viewlevels View access levels, each with its JSON array of group IDs.

9.2 The Access Class

All calculation lives in Joomla\CMS\Access\Access, with Rules and Rule as the value objects that merge the JSON. The methods you will actually use:

use Joomla\CMS\Access\Access;

Access::check($userId, 'core.edit', 'com_content.article.42');
Access::checkGroup($groupId, 'core.manage', 'com_content');
Access::getAuthorisedViewLevels($userId);   // e.g. [1, 2, 5]
Access::getGroupsByUser($userId);           // includes inherited parents
Access::preload('com_content');             // warm the cache for a component

In everyday component code you rarely call Access directly; you ask the user object, which delegates and caches:

$user = $this->getUserFactory()->loadUserById($userId);

if ($user->authorise('core.edit.state', 'com_content.article.42')) {
    // may publish or unpublish this article
}

9.3 Filtering Lists by View Level

Permission checks guard actions; view levels guard queries. Every core content query filters on the user's levels, and yours should too:

$levels = $user->getAuthorisedViewLevels();

$query->whereIn($db->quoteName('a.access'), $levels);

Forgetting this filter is how "hidden" content leaks into custom modules and search results: the item was protected, but the query never asked.

9.4 Performance: One Tree, Well Cached

ACL looks expensive - a JSON merge across a tree for every check - but Joomla caches aggressively per request: the user's groups, the asset rules, and each authorise() verdict are computed once. The practical advice: call Access::preload() when you know you will check many items of one component, and never loop a thousand items calling a fresh check each time on cold caches.

Back to top

10. ACL in Your Own Extension

10.1 Declare Your Actions: access.xml

A component announces which actions it supports in an access.xml file at its admin root. A minimal, real-world shape (this mirrors what com_content ships, which declares extra sections for categories, articles, fields, and workflow stages):

<?xml version="1.0" encoding="UTF-8"?>
<access component="com_example">
    <section name="component">
        <action name="core.admin" title="JACTION_ADMIN" />
        <action name="core.manage" title="JACTION_MANAGE" />
        <action name="core.create" title="JACTION_CREATE" />
        <action name="core.delete" title="JACTION_DELETE" />
        <action name="core.edit" title="JACTION_EDIT" />
        <action name="core.edit.state" title="JACTION_EDITSTATE" />
    </section>
    <section name="item">
        <action name="core.edit" title="JACTION_EDIT" />
        <action name="core.edit.state" title="JACTION_EDITSTATE" />
    </section>
</access>

You are not limited to the standard set. A component can declare its own actions - an approval right, an export right - and check them like any other permission. Give custom actions your own prefix and leave core. to the standard set:

<action name="example.approve" title="COM_EXAMPLE_ACTION_APPROVE" />
if ($user->authorise('example.approve', 'com_example')) {
    // this user may approve items
}

The custom action appears automatically in the Permissions tab next to the standard ones, and administrators grant it per group like any other action. This is how review workflows, export rights, and similar role-specific abilities are built without inventing a permission system of your own.

10.2 Show the Permissions Tab: config.xml

The familiar Permissions tab appears in your component's Options as soon as its config.xml contains the rules field:

<fieldset name="permissions" label="JCONFIG_PERMISSIONS_LABEL">
    <field name="rules" type="rules"
           label="JCONFIG_PERMISSIONS_LABEL"
           component="com_example"
           section="component" />
</fieldset>

Joomla reads the sections and actions from your access.xml (via Access::getActionsFromFile()) and renders the whole matrix for you.

10.3 Per-Item Assets Come Almost Free

If your item table extends Joomla's Table class and carries an asset_id column, the framework creates and maintains the asset row on save: it computes the asset name (com_example.item.7), finds the parent asset, and stores the rules from the permissions field. Your job is only to check the right asset name in your controllers and views, at the deepest level that exists:

$assetName = 'com_example.item.' . (int) $item->id;

if (!$user->authorise('core.edit', $assetName)) {
    throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}

Remember the lesson from the security hardening article: hiding a toolbar button is cosmetics. The authorise() call in the code is the actual protection.

Back to top

11. ACL and the Web Services API

11.1 The Same Rules, No Exceptions

The Web Services API does not have its own permission system. An API request authenticates as a user (usually via a token), and from then on every endpoint enforces the exact same ACL as the website: the same groups, the same assets, the same deny rules. There is exactly one extra gate: the group needs core.login.api allowed on the Global Configuration level, or authentication fails before any endpoint runs.

11.2 Managing ACL Through the API

Users, groups, and view levels are themselves manageable over REST:

curl -H "X-Joomla-Token: <token>" \
     https://example.test/api/index.php/v1/users

curl -H "X-Joomla-Token: <token>" \
     https://example.test/api/index.php/v1/users/groups

curl -H "X-Joomla-Token: <token>" \
     https://example.test/api/index.php/v1/users/levels

All three support the usual CRUD verbs, so provisioning tools can create groups and levels programmatically. Treat these endpoints with respect: whoever can POST to v1/users/groups can reshape your entire permission model. Keep API access limited to dedicated, minimal accounts, exactly as in the recipe in section 7.4.

Back to top

12. SEO and Metadata

ACL has a quiet but real relationship with SEO. Content behind a view level is invisible to search engine crawlers, which browse as guests: a page set to Registered simply does not exist for Google. That is usually the intent - but it also means an accidental access level change can silently de-index a whole section. If a page's traffic collapses, checking its view level takes ten seconds and has solved more "SEO mysteries" than any meta tag.

The reverse mistake also happens: teaser pages meant for everyone accidentally inherit a restricted level from a menu item or parent category, or a custom module leaks premium content into public search results because its query forgot the view-level filter (section 9.3). Review what a logged-out visitor - and therefore a crawler - can actually see after every ACL change that touches public areas.

Back to top

13. Common Mistakes and Pitfalls

13.1 Using Denied Where Not Allowed Already Works

Symptom: months later, a group mysteriously cannot do something no matter what you allow.

Fix: remember that deny is final down the whole tree. Audit for explicit denies (the SQL in section 8.3 finds them) and replace them with plain "Inherited" wherever the default "Not Allowed" already does the job.

13.2 An Extra Group Takes Rights Away

Symptom: you add a user to a second group to give them more access, and they lose abilities instead.

Fix: one of the new group's rules carries a deny, and any deny among a user's groups vetoes the allows of the others. Check both groups in Debug User and remove the deny.

13.3 Forgetting Super Users in a Custom View Level

Symptom: administrators report that premium content is "missing" while members see it fine.

Fix: view levels contain only the groups you put in them - Super Users bypass permissions, not view levels. Add the Super Users group to every custom level.

13.4 Granting core.admin Casually

Symptom: a "content" group can suddenly change permissions, or worse, has become a full super-user group.

Fix: core.admin on a component hands over that component's ACL; on the Global Configuration it creates Super Users. Grant it almost never, and audit who has it today.

13.5 Solving Visibility Problems with Permissions

Symptom: a tangle of item-level permission rules that only needed to hide content from some visitors.

Fix: visibility is the job of view access levels; permissions are for actions. If the requirement contains the word "see", think levels first.

13.6 Direct Database Edits Without a Rebuild

Symptom: after importing content or editing groups in SQL, permissions apply to the wrong things.

Fix: both #__assets and #__usergroups are nested sets whose lft/rgt values must stay consistent. Use the Rebuild button under Users → Groups and the database repair tools, and avoid hand-editing tree tables at all.

13.7 Expecting Administrators to Be Almost Super Users

Symptom: a user in the Administrators group cannot open the Global Configuration, cannot install extensions, and sees every custom field greyed out in the article edit form - the Permissions tabs show "Edit Custom Field Value" as Not Allowed (Inherited) everywhere, including on every newly created field.

Fix: nothing is broken; these are the shipped defaults (section 5.2). root.1 grants Administrators only core.manage, core.admin belongs to Super Users alone, com_installer carries an explicit deny for group 7, and no group at all is granted core.edit.value. Grant what your administrators really need, deliberately: set Edit Custom Field Value to Allowed at the component level (for example Content → Articles → Options → Permissions) so all fields inherit it. If Administrators must also install extensions, a Super User has to replace the explicit Denied on com_installer itself (System → Install → Extensions, then Options → Permissions) - remember from section 3.2 that no deeper allow can ever override it.

Back to top

14. Best Practices

If you remember only a few things from this article, remember these:

  • Grant permissions to groups, never think in individual users; create a group per role, not per person.
  • Build up with Allowed and Inherited; treat Denied as a last resort, because it is final all the way down.
  • Set permissions as high in the hierarchy as possible - global for roles, component for areas, category for departments - and keep item-level rules rare.
  • Use view access levels for "who sees this" and permissions for "who does this"; do not mix the two jobs.
  • Add Super Users to every custom view level you create.
  • Keep the circle of core.admin holders (and thus Super Users) as small as possible, and give API integrations their own minimal group.
  • After every ACL change, verify as the affected user - the Debug User view or a test account tells the truth.
  • Developers: declare actions in access.xml, check authorise() on the deepest asset, and filter every query by view levels.
Back to top

15. Quick Reference

MODEL        groups   = who you are        (#__usergroups, nested)
             levels   = what you can SEE   (#__viewlevels, group id list)
             actions  = what you can DO    (rules JSON on #__assets)

STATES       Inherited    no opinion, ask the parent
             Allowed      grant here
             Denied       forbid here - FINAL, wins over any allow below
             Not Allowed  the default result when nothing was granted

HIERARCHY    root.1 (Global Config) > component > category > item
             group tree inherits in parallel; any deny vetoes

ASSETS       name pattern: com_content.article.42
             rules: {"core.edit":{"4":1}}   action > group > 1/0

SUPER USER   any group with core.admin allowed on root.1
             bypasses permissions, NOT view levels
             emergency: root_user in configuration.php

ADMINS (7)   default: core.manage on root.1, most components
             NO Global Configuration (core.admin = 8 only)
             NO extension installer  (com_installer denies 7)
             NO custom field values  (core.edit.value never granted)

DEBUG        calculated setting column   (save first, then read)
             Users > Manage > Debug User / Debug Group
             SELECT name, rules FROM #__assets WHERE rules LIKE '%:0%';

RECIPES      department:  allow on their category only
             frontend mgr: content rights, no core.login.admin
             members area: group + view level, no permissions
             API user:     own group, core.login.api + one action

API          v1/users, v1/users/groups, v1/users/levels
             API requests obey normal ACL + core.login.api gate

DEV          access.xml declares actions
             <field type="rules"> shows the tab
             $user->authorise('core.edit', $assetName)
             filter queries by getAuthorisedViewLevels()
Back to top

16. Summary

Joomla's ACL is a layered but consistent system:

  • Three mechanisms answer three questions: groups (who you are), view levels (what you see), permissions (what you do).
  • Four states drive every dropdown, and the golden rule is that an explicit Denied is final down the entire tree.
  • The hierarchy runs from Global Configuration through component and category to item, stored as an assets tree with JSON rules in #__assets.
  • The calculation merges all of a user's groups: allows accumulate, but a single deny vetoes, and core.admin on the root makes a Super User who skips the walk entirely.
  • The defaults are stricter than most people expect: Administrators get core.manage but no Global Configuration, an explicit deny on the extension installer, and - like every group except Super Users - no right to edit custom field values.
  • Debugging is a reading exercise: the calculated column, the Debug User view, and three SQL queries reveal any rule on the site.
  • Developers get the whole machine - actions, the permissions tab, per-item assets - by shipping an access.xml, one form field, and honest authorise() checks.

Once you can name the mechanism a requirement belongs to - see it, do it, or be it - Joomla ACL stops being scary. The dropdowns become sentences you can read, and the site becomes a place where you can prove who may do what.

That said, ACL is also the part of Joomla where a small early mistake grows quietly for years. If your site has accumulated mystery denies, super-user groups nobody remembers creating, or permission tabs no one dares to touch, an experienced structured ACL audit - groups, levels, assets, and rules, in that order - usually restores both clarity and safety in a single afternoon. It is precisely the kind of work a Joomla specialist enjoys.

Back to top
Joomla ACL Explained: Groups, Access Levels and Permissions
Peter Martin
Peter Martin
Joomla Specialist

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.