Understanding User Extended Fields

Create, store and access custom information using the MODX User’s Extended Fields

By Bob Ray
February 2, 2022
Understanding User Extended Fields

You may have used the Extended Fields in MODX user profiles in the MODX Manager, where MODX provides a convenient interface for them. To use extended fields in code, however, it helps to understand a bit about how they are stored.

Most of the fields of MODX objects like Resources, Users, Chunks, Snippets, etc. are stored as either integers or strings of text. When you call an object’s get() method in code, xPDO checks the dbType of the object and returns something appropriate. In an earlier blog post, we saw how calling get() on a date field of a resource such as createdon or publishedon returns a human-readable time/date string even though the value stored in the database is an integer representing a unix timestamp.

Sometimes, though, the field in the database needs to store more information than you could put in a simple string or integer. JavaScript Object Notation (JSON) is just the tool for this.

JSON Strings

JSON is a way of storing complex data in the form of a string. Note that you don’t really have to understand JSON to handle extended fields. Because of the way MODX handles them, the JSON will never exist in your code. The explanation below is partly to satisfy the curious, but it’s also good to have a little understanding of JSON because it can be used elsewhere in MODX to specify criteria for queries in various MODX Extras like getResources and pdoResources.

A JSON string can hold several types of data, but the one we’re interested in here is usually a simple JSON object, which is a series of key/value pairs in this form:

{"favoriteColor":"blue","favoriteNumber":"12"}

The JSON is easier to understand if we format it like this:

{
   "favoriteColor":"blue",
   "favoriteNumber":"12"
}

If these were extended fields in a user profile, this user’s favorite color would be blue and his or her favorite number would be 12. Notice that the curly braces enclose the entire list (technically, a JSON object), the key/value pairs are separated by a colon, and the pairs are separated by a comma (with no comma at the end of the list).

The extended field can also hold nested sets of values. This is not often done, but suppose that in addition to the favorites listed above we also wanted to store the names and ages of the user’s children. On the user’s “Extended Fields” tab in the Manager, you could create a JSON object (aka “container”) called “children” and add an attribute for each child (by right-clicking on the “children” container) with the child’s name as a key and the child’s age as the value. That would give us a JSON string that looked like this:

{"favoriteColor":"blue","favoriteNumber":"12","children":{"Bobby":"3","Susie":"5"]]

or reformatted to show the structure:

{
    "favoriteColor":"blue",
    "favoriteNumber":"12",
    "children":
        {
            "Bobby":"3",
            "Susie":"5"
        }
}

Displayed in this form, you can see that children is a key just like favoriteColor and favoriteNumber, but its value is another JSON object—a list with two key/value pairs of its own.

Extended Fields in Code

If you look at the fields of the modUserProfile object here, you’ll see that the type of the extended field is listed as json. When you use this code:

$profile = $modx->user->getOne('Profile');
$extended = $profile->get('extended');

MODX (xPDO actually) sees that the type of the extended field is json and automatically calls $modx->fromJSON on it before returning the result. What comes back is a regular PHP associative array. In our first example, that would look like this if you called echo print_r($extended, true) on the $extended variable:

Array
(
    [favoriteColor] => blue
    [favoriteNumber] => 12
)

The array from our second example would look like this:

Array
(
    [favoriteColor] => blue
    [favoriteNumber] => 12
    [children] => Array
        (
            [Bobby] => 3
            [Susie] => 5
        )
)

When you save the extended field with $profile->set('extended', $someArray) xPDO does the conversion in reverse by calling $modx->toJSON() on the value before saving it to the database. This means that you never have to deal with the JSON strings yourself when getting and setting extended fields in code.

In theory, the extended field can be a nested set of arrays with as many levels as you want. Each child could have the child’s name as a key with a value that was an array containing the child’s age, birthday, height, and weight. In practice, deeply nested arrays can be fairly challenging to work with.

Setting Placeholders

Setting MODX placeholders from the extended array is a common task. The easiest way to do that is to use the Profile Snippet, which is part of the Login package. If the Login package is installed, all you need to do is put this tag at the top of your page. Once you’ve done that, the username, id, and all profile fields, including any extended fields will be set as placeholders. For security, the Profile Snippet does not set any of the password fields as placeholders.

[[!Profile]]

If you want to set the placeholders for some other user, you can add the username or user ID to the Snippet tag. You can also add an optional prefix that will be prepended to all placeholders:

[[!Profile? &user=`BobRay` &prefix=`profile.`]]

That means that the tag used to display the user’s email would be:

[[+profile.email]]

If you just want the extended fields set as placeholders, without setting the other fields of the user profile, you’ll have to do it yourself, though the code is very simple:

[[!SetProfilePlaceholders]]
/* SetProfilePlaceholders snippet */
$usr = $modx->user;
/* Optional code to get some other user:
   $usr = $modx->getObject('modUser',
       array('name' => 'BobRay'));
*/
$profile = $usr->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $modx->toPlaceholders($extended, '');
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

The second argument to toPlaceholders() is an optional prefix to be prepended to all placeholders. Use it if you think there’s a chance that the placeholders will conflict with other placeholders on the page. If you use a prefix here, you don’t need to include the dot that separates the prefix from the placeholder name—MODX will add it automatically. (We used the dot above in tag for the Profile Snippet because it will not automatically add the separator.) If you want to use some other separator, you can include it as a third argument to toPlaceholderd(), but there’s seldom any reason for doing so.

Remember that the placeholder name will be the key for that member of the extended field you want to display. You can see the keys by editing the extended fields section of a users' profile in Manage -> Users on the MODX top menu.

Changing Extended Fields

Sometimes, you want to change the value of an extended field in code, maybe as a result of the user filling out a form with their preferences. The trick is to remember that you should always get the extended array first before making any changes to it. If you set an extended field value without doing this, you’ll wipe out any other extended fields when you save the profile. Other extended fields might have been set by an Extra without you knowing it, so it’s always a good idea to get the fields first before changing any of them.

Say, for example, that you want to change the user’s favorite color from blue to red. The code to do that would look like this:

$usr = $modx->user;
/* see code above for getting another user */
$profile = $user->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $extended['favoriteColor'] = 'red';
    $profile->set('extended', $extended);
    $profile->save();
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

For nested extended arrays, you can do the same thing by setting the appropriate field of the array. For our second example above, we could change Susie’s age by replacing line six above with this:

    $extended['children']['Susie'] = '6';

Adding Extended Fields

After creating extended fields in the Manager, you might think that there’s more to them than there really is. The JSON string stored in the extended field of the user profile is all there is to them. Because of xPDO’s slick way of handling them, all you need to do is to add one or more new members to the array before calling set() and any fields you’ve added will be extended fields just like any of the others.

For example we could turn our first example above into the second example just by doing this:

$usr = $modx->user;
/* see code above for getting another user */
$profile = $user->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $extended['children'] = array(
        'Bobby' => '3',
        'Susie' => '5'
    );
    $profile->set('extended', $extended);
    $profile->save();
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

Bulk Adding Extended Fields

Suppose you want to add the extended fields from our first example above to all user profiles. It’s a royal pain to load each user and create them manually in the Manager. It’s much easier to write simple utility Snippet to do it for you. We’ll leave the values blank. You can set them if you like, but if you do that, every user will get the same values. You can put a tag for the Snippet on a temporary resource and view the Resource to run the Snippet:

[[!AddExtendedFields]]
/* AddExtendedFields snippet */
$users = $modx->getCollection('modUser');
foreach($users as $user) {
    $profile = $user->getOne('Profile');
    if ($profile) {
        $extended = $profile->get('extended');
        $extended['favoriteColor'] = '';
        $extended['favoriteNumber'] = '';
        $profile->set('extended', $extended);
        if ($profile->save()) {
            echo '<br/>Processed user: ' .
                $user->get('username');
        } else {
            echo '<br />Error saving user: ' .
                $user->get('username');
        }
    } else {
        echo '<br />Could not find profile for user: ' .
            $usr->get('username');
    }
    echo '<br />Finished!';
}

A Word of Warning

When setting placeholders from the user profile, it’s tempting to do something like this:

/* Don't do this! */
$profile = $modx->user->getOne('Profile');
$fields = $profile->toArray();
foreach($fields as $key => $value) {
    $modx->setPlaceholder($key, $value);
}

This seems safe enough, but it’s not. Not only will the extended placeholders not be set, if E_NOTICE errors are enabled, you’ll get an “array to string conversion” error. This happens because the extended field is itself an array. The setPlaceholder() method expects a string, not an array, as its second argument. When the loop gets to the extended field, things will go sideways. The toPlaceholders() method handles this properly, but setPlaceholder() won’t.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.