MODX pays a huge, ongoing architectural price for features that most projects barely use
This is not a moral judgment or an attempt to declare complexity inherently bad. It is about a very specific engineering problem: how justified is the cost of an architectural capability relative to how often and how deeply it is actually used.
In MODX, this problem is particularly visible in its system of contexts and access policies.
The idea was rational
Contexts in MODX were conceived as a way to divide an application into independent areas: web, mgr, additional sites, language versions, restricted sections. Each context could have its own settings, policies, users, permissions, resource map, and runtime state.
At the same time, MODX built a universal ACL model. Permissions could be assigned not just to resources, but also to element categories, the elements themselves, TVs, Media Sources, namespaces, menus, and other system entities.
To achieve this, the core included (and partially still includes) a large set of specialized models and tables:
modx_access_contextmodx_access_resourcesmodx_access_resource_groupsmodx_access_categorymodx_access_elementsmodx_access_templatevarsmodx_access_media_sourcemodx_access_namespacemodx_access_menus- historically —
modx_access_actions,modx_access_actiondom, and other related entities.
From an engineering perspective, this is a powerful idea: a single unified security framework can be applied to very different objects.
But universality went too deep into the core
The problem begins the moment this flexibility ceases to be an optional add-on and becomes a fundamental part of the runtime model.
Access to MODX objects is tied to policies, user groups, roles, authority, target objects, and a specific context. Any complex security scenario quickly turns into a multidimensional scheme:
User
× User Group
× Role / authority
× Context
× Policy
× Resource Group
× Element Category
× Media Source
× target object
Such a graph can still be justified on its own if it is widely used. But in practice, the vast majority of projects live much simpler lives: one web, one mgr, one administrator, a few editors, sometimes Resource Groups, and almost never the full scope of object-level ACLs across all entity types.
Yet the core is still obligated to support this entire model continuously.
The context proved to be too "heavy" of an abstraction
A context in MODX is not just a site identifier or a set of settings. It ties together:
- settings;
- policies;
- resource maps;
- aliases;
- plugins;
- security state;
- authentication state;
- cache;
- context-specific runtime.
Even user identity within a single PHP session can be different in different contexts:
{
"modx.user.contextTokens": {
"mgr": 1,
"web": 642
}
}
That is, a single physical session allows for a situation where the user is one person in the Manager, and another on the frontend.
Technically, this is flexible. Practically, it adds yet another layer of logic that most developers never consciously use, but which remains part of the core semantics.
Why all of this had to be aggressively cached
A full recalculation of effective permissions on every request to an object would be far too expensive. Therefore, MODX is forced to cache the computed security state at multiple levels.
Jason Coward, the chief architect of MODX Revolution, directly explained that access policies are cached "for obvious performance reasons" both on the target objects' side and in the user session.
Hence structures like these appear in the session:
modx.user.*.attributes
modx.user.*.resourceGroups
modx.user.contextTokens
Contexts, in turn, have their own cache, which stores settings, policies, and other context-specific data. Objects may have their own policy cache.
The result is a logical chain:
rich ACL model
→ expensive permission resolution
→ caching of effective state
→ session cache + context cache + object cache
→ distributed security state
→ complex invalidation
In other words, caching here is not a refutation of the system's heaviness, but a direct consequence of it.
The performance cost turns into a maintenance cost
When effective permissions are smeared across the database, user session, context cache, and object cache, changing permissions is no longer just a matter of altering an ACL record.
Hence the historically familiar MODX procedures:
Flush Permissions
Flush Sessions
clear cache
re-login
This is not an accidental UX quirk. It is a consequence of the fact that the security state had to be denormalized for the sake of performance.
Rarely used tables remain part of the system anyway
Many modx_access_* tables in typical projects are empty or nearly empty simply because end developers do not use the corresponding features.
However, an empty table does not mean a zero architectural cost.
As long as the corresponding feature exists in the core, you must maintain:
- the model;
- the schema;
- relationships;
- loaders;
- processors;
- permission resolution;
- cache semantics;
- backward compatibility;
- migrations;
- testing;
- edge case diagnostics.
That is, the cost is paid not by the number of rows in a table, but by the very fact that the subsystem exists.
The cost of competence
There is another cost that is often underestimated: the requirements placed on a person capable of safely modifying the core.
To understand auth, ACL, or caching issues in MODX, it is not enough to know PHP, or even to know the public MODX API well.
You need to understand:
- contexts;
- user sessions;
- contextTokens;
- policies;
- policy templates;
- roles and authority;
- User Groups;
- Resource Groups;
- Element Category ACLs;
- Media Source ACLs;
- object lifecycles;
- processors;
- xPDO hydration;
- context cache;
- object cache;
- permission invalidation;
- backward compatibility for old Extras.
And the more rarely used features remain in the core, the higher the minimum level of competence required for someone to accept or reject a change in the core.
This directly impacts not just the code, but the project's evolution: tickets and pull requests pile up faster than the small group of people who truly understand all the implications of these changes can process them.
A strong idea doesn't have to live forever
It is important not to confuse criticism of the result with a rejection of the original idea.
MODX's universal ACL model was ambitious and truly powerful in many scenarios. The ability to finely restrict access to resources, elements, file sources, and different contexts is a real functionality, not a fiction.
However, an architectural capability must periodically undergo a re-evaluation of its value.
A healthy platform evolution looks roughly like this:
idea
→ implementation
→ usage
→ evaluation of real demand
→ reinforcement of what works well
→ simplification or removal of unrequested parts
If a system only knows how to add features but almost never knows how to drop them, it begins to accumulate not just legacy code, but permanent obligations to support old architectural hypotheses.
This is precisely where legacy emerges—not as age, but as the system's inability to forget.
Why this is worth discussing based on facts
Talk of legacy quickly turns into disputes over the reputation of technologies, teams, and individuals. This almost always degrades the quality of analysis.
I discuss the general principle separately in the concept AI as reputation arbiter: one should evaluate observable mechanisms, consequences, and verifiable facts, rather than trying to automatically assign someone the status of a "correct" or "incorrect" participant in the discussion.
In the case of MODX, the question can be formulated without moralizing:
Is the ongoing architectural cost of a complex multicontext ACL system justified by the frequency and depth of its actual usage?
My conclusion is that in many practical scenarios, it hasn't been for a long time.
This is precisely why MODX today pays a huge, ongoing architectural price for features that most projects barely use.