Custom Users
Last updated Oct 19th, 2026 | Page history | Improve this page | Report an issue
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$194 per month—let's make that $500!
Learn moreIntended Audience¶
For developers who need extra data on their MODX users and extra functionality on the related classes. The steps below are technical and rely on xPDO: familiarity with xPDO objects and methods (e.g. getObject) is required.
The quick way out is a separate table with a foreign key back to modx_users. This article covers the deeper integration instead: extending the core modUser class.
See Also¶
- Reverse Engineer xPDO Classes from Existing Database Table
- More Examples of xPDO XML Schema Files
- Generating the Model Code
The Rules¶
Extending modUser does not mean writing to the modx_users table. You add a separate related table and bind it to the original table by a foreign key. Never replace the modUser class outright: build on it.
The only trace of an extended user is its class_key — changed from MODX\Revolution\modUser (the default in 3.x; modUser in 2.x) to the extended class name.
Key rules for extending modUser:
- Use your extension only to access your extra data. Do not interact with user objects that have not been extended.
- Do not replicate existing
modUserfunctionality. Use the built-in user handling instead. - Learn
modUserandmodPrincipalbefore coding. Some of their methods (attributes, for one) work per context or resource. - Work with attributes through
modPrincipal::loadAttributes()/getAttributes(). They are driven by theprincipal_targetssystem setting. - Access
modUsermethods throughmodUsersuggestions.
Steps to extend modUser¶
1. Create the extended user schema and generate a model¶
Create an extended user schema that extends modUser. There is no aggregate relation upwards from your "main" class which is extending modUser.
The table prefix for the ancillary table is set in the <model> node.
Simple Example¶
We add a single extra attribute (e.g. a facebook_url) to the user data. In the database this means a separate table with 2 columns: a foreign key back to the modx_users table and the new attribute:
<?xml version="1.0" encoding="UTF-8"?>
<model package="extendeduser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="ext_">
<!-- extend the modUser class -->
<object class="extUser" extends="modUser">
<composite alias="Data" local="id" class="Userdata" foreign="userdata_id" cardinality="one" owner="local" />
</object>
<object class="Userdata" table="data" extends="xPDOSimpleObject">
<field key="userdata_id" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
<field key="facebook_url" dbtype="varchar" precision="100" phptype="string" null="true" />
<index alias="userdata_id" name="userdata_id" primary="false" unique="true" type="BTREE">
<column key="userdata_id" length="" collation="A" null="false" />
</index>
<aggregate alias="extUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
</object>
</model>
The extension of modUser happens within a single object node.
More Involved Example¶
Deprecated: index="unique" — declare indexes in their own node instead:
<?xml version="1.0" encoding="UTF-8"?>
<model package="extendeduser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="ext_">
<!-- inherit the modx user and extend it -->
<object class="extUser" extends="modUser">
<composite alias="Phones" local="id" foreign="user" cardinality="many" owner="local" />
<composite alias="Table2" local="id" foreign="user" cardinality="many" owner="local" />
</object>
<!-- track all user phone numbers -->
<object table="phone_numbers" extends="xPDOSimpleObject">
<field key="user" dbtype="int" phptype="integer" null="false" default="0" index="index" />
<field key="areacode" dbtype="varchar" precision="3" phptype="string" null="false" default="" />
<field key="number" dbtype="varchar" precision="7" phptype="string" null="false" default="" />
<aggregate alias="extUser" local="user" foreign="id" cardinality="one" owner="foreign" />
</object>
<!-- user extension -->
<object table="table2" extends="xPDOSimpleObject">
<field key="user" dbtype="int" phptype="integer" null="false" default="0" index="index" />
<field key="myspaceurl" dbtype="varchar" precision="255" phptype="string" null="false" />
<aggregate alias="extUser" local="user" foreign="id" cardinality="one" owner="foreign" />
</object>
</model>
Parse and create the model map for this schema. The process is out of scope here: see Using Custom Database Tables in your 3rd Party Components.
2. Edit the extended user class¶
Set class_key in _modx_users to your extended class (e.g., extUser). Default is MODX\Revolution\modUser.
Edit the generated extuser.class.php (found in model/mysql/):
<?php
/**
* @package extendeduser
* @subpackage user.mysql
*/
use MODX\Revolution\modUser;
class extUser extends modUser {
function __construct(xPDO $xpdo) {
parent::__construct($xpdo);
$this->set('class_key','extUser');
}
}
?>
3. Create (or edit) extension_packages in System Settings¶
In System settings (System menu), search for extension_packages.
If the key already exists, add inside the JSON array:
,{"extendeduser":{"path":"[[++core_path]]components/extendeduser/model/"}}
If the key does not exist, create a new system setting:
- Name:
extension_packages - Key:
extension_packages - Fieldtype: Textfield
- Value:
[{"extendeduser":{"path":"[[++core_path]]components/extendeduser/model/"}}]
4. Create a class to access and utilize your extended user class¶
To interact with your extended data, create a class to access it. You'll typically do this in a Snippet, Plugin, or CMP.
Snippet Example¶
Here's how you might interact with your extended data in a Snippet:
$modx->addPackage('extendeduser', MODX_CORE_PATH . 'components/extendeduser/model/', 'ext_');
$user = $modx->getObject('extUser', 123); // where 123 is the id of a user
$data = $user->getOne('Data'); // use the alias from the schema
// toArray will print all the extra data, e.g. facebook_url
return print_r($data->toArray(), true);
More complex example¶
<?php
/**
* File sample.class.php (requires MODX Revolution 2.x)
* Created on Aug 18, 2010
* Project shawn_wilkerson
* @package extendedUser
* @version 1.0
* @category User Extension
* @author W. Shawn Wilkerson
* @link http://www.shawnWilkerson.com
* @copyright Copyright (c) 2010, W. Shawn Wilkerson. All rights reserved.
* @license GPL
*
*/
if (!class_exists('Extendeduser')) {
class Extendeduser
{
function __construct(modX $modx, array $config= array ()) {
/* Import modx */
$this->modx= $modx;
/* Establish the environment */
$this->extPath= $modx->getOption('core_path',null, MODX_CORE_PATH).'components/extendeduser/';
$this->modx->addPackage('extendeduser', $this->extPath .'model/', 'ut_');
$this->_config= array_merge(array (
'userID' => $this->modx->user->get('id'),
), $config);
/* Define the user */
$this->userObj = $this->setUser($this->_config['userID']);
$this->userID = $this->userObj->get('id');
}
function __destruct() {
unset ($this->extPath, $this->userObj, $this->userID, $this->_config);
}
/**
* Returns object of type Phone.
*/
public function getPhoneObj() {
$this->userObj->getOne('Phones');
return $this->userObj->Phones;
}
/**
* Returns object utUser instance of modUser Defaults to current user.
* @param $userID
*/
public function getUserObj($userID) {
return $this->modx->getObject('modUser', $userID);
}
/**
* Establishes the user.
* @param int $userID
*/
public function setUser($userID){
return $this->getUserObj($userID);
}
}
}
5.) Accessing the extended user class¶
In our example we will be accessing our extended user throughout our site, therefore we load it as a service as shown in the following example:
<?php
$x = $modx->getService('extendeduser','Extendeduser',$modx->getOption('core_path',null, MODX_CORE_PATH).'components/extendeduser/',$scriptProperties);
if (!($x instanceof Extendeduser)) {
$modx->log(modX::LOG_LEVEL_ERROR,'[Extendeduser] Could not load Extendeduser class.');
$modx->event->output(true);
}
return;
Noteworthy items¶
- Any pre-existing user will still have
modUseras theclass_keyand therefore will not be extended or produce user objects of typeextUserunless you change it. -
modUser::setSudo()– the preferred way to set the privilegedsudoflag, since mass‑assignment ofsudois blocked inmodUser::set(). -
modUser::save()is overridden to fire MODX‑specific events (e.g.OnUserBeforeSave) and to setcreatedonfor new users. Be aware that saving an extended user will trigger these events. - Double‑check the
modx.mysql.schema.xmlfile to ensure you are not colliding with existing class names or aliases; otherwise your extensions may supersede the defaultmodUserdefinitions (including attributes likeProfile). - The
extUserwill not have a table created in the database, but the attached relations will. - The extended class table(s) must be in the same database as the regular
_modx_\_userstable. - Symptoms of step 3 (extension _packages path) not being correct:
- Any user with the
class_keyofextUserwill return an error upon login: "User cannot be found…". If this is the admin, access your database directly, revert theclass_keytoMODX\Revolution\modUser, log in correctly, and then correct the path. - The snippets attached to the class may intermittently work or fail altogether.
- Any user with the
- To get counts from your data (i.e. how many phone numbers does this person have) use either (any criteria can be added):
$this->modx->getCount('extPhones', array('user' => $this->userID));
$this->modx->getCount('extPhones');
Multiple extended modUser systems can run side by side: repeat the process for each extension, changing class_key to the respective extended class. They can even be combined — for example, by extending the rpx extension into a hybrid system.
Suggested additional considerations¶
- Model files can be edited with methods and descriptions (see MODX/xPDO models).
- Automate on user login: see splittingred's modActiveDirectory:
- Plugin:
plugin.activedirectory.php - Events:
onauthentication.php,onusernotfound.php
- Plugin:
Extended modUser Classes currently Available¶
modActiveDirectory provides interaction with a Microsoft Domain Controller.
rpx extension allows people to login via Facebook and other social networking mediums.
Modifying class_key¶
Whenever you modify the class_key for a built-in MODX object, you need to be aware of how behavior changes. The class_key affects what aggregates and composites are available to the object. For example, if a user has a class_key "extUser", you can still retrieve the object using the parent class:
// both of these work when class_key is "extUser":
$User = $modx->getObject('modUser', 123);
$User = $modx->getObject('extUser', 123);
However, the aggregates or composite relationships depend on the stored value of the class_key.
$Data = $modx->newObject('Userdata');
$Data->set('facebook_url',$url); // ... etc ...
$User->addOne($Data);
$User->save(); // this will not save related data if the class_key does not have the relations defined!
$User->set('class_key', 'extUser');
$User->save(); // now we can set related data
$User->addOne($Data);
$User->save(); // and now we can save related data
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$194 per month—let's make that $500!
Learn more










