Displaying Hidden Resource Fields with the IntlDateFormatter

Using the IntlDateFormatter to show MODX hidden fields in the Manager.

By Bob Ray  |  August 11, 2026  |  11 min read
Displaying Hidden Resource Fields with the IntlDateFormatter

In my two previous articles(1,2), we saw how to use PHP's IntlDateFormatter to display dates in a format that is typical for different locales. In this one, we'll circle back to how to use the techniques from those articles to display MODX hidden fields in the Manager's Create/Edit Resource panel. The code will be same code we used in my first article in this series with a couple of added functions to get the timestamps quickly, and format the dates with the IntDateFormatter. The only changes from our original code are the addition of the two functions, and the code of the 'date' section of the switch statement.

Getting a Timestamp the Fast Way

As I mentioned in a previous article, MODX object date fields are stored in the database as Unix timestamps. When you ask for them with $object->get('dateFieldName'), MODX returns a human-readable date. Since it's recommended to use a timestamp, we originally converted that human-readable date with PHP' strtotime() function. This is quite inefficient and somewhat offensive to most PHP coders. Here is a function that will return the raw timestamp from any MODX date field. It's mostly relevant for Resources, but could be used with Settings and TVs that have a date field for their value:

function getObjectTimestamp($modx, $object, $objectId, $field) {
    /* $object should be a fully qualified object for either
       MODX 2 or MODX 3+, e.g., `modResource` for MODX2
       or `MODX\Revolution\modResource` for MODX 3+ */

    $query = $modx->newQuery($object, $objectId);
    $query->select($field);
    $timestamp = $modx->getValue($query->prepare());
    return $timestamp;
}

We send the modX object as the first argument to the function above, so we don't need to use global $modx in the function.

DateFormatter Function

This function returns a date formatted by the InlDateFormatter. It will handle using the pattern method we saw in my previous article or the built-in date and time formats we saw, in the article before that.

function dateFormatter(
    $timestamp,
    $locale,
    $dateFormat,
    $timeFormat,
    $timezone = NULL,
    $calendar = IntlDateFormatter::GREGORIAN,
    $pattern = NULL,
    $method = 'constants') {

    /* $method should be either 'constants' or 'pattern' */
    /* $pattern (if any) should be a "skeleton" like 'yMMMMdHms' */

    switch ($method) {
        case 'constants':
            $dateFormatter = new IntlDateFormatter(
                $locale,
                $dateFormat,
                $timeFormat,
                $timezone,
                $calendar,
                $pattern,
            );
            $output = $dateFormatter->format($timestamp);

            break;

        case 'pattern':
            $patternGenerator = new IntlDatePatternGenerator($locale);
            // Ask for the best localized pattern for the skeleton
            $bestPattern = $patternGenerator->getBestPattern($pattern);

            $dateFormatter = new IntlDateFormatter(
                $locale,
                IntlDateFormatter::NONE, // ignored
                IntlDateFormatter::NONE, // ignored
                $timezone,
                $calendar,
                $bestPattern // The generated pattern
            );
            $output = $dateFormatter->format($timestamp);
            break;

        default:
            return 'Unknown Method';

    }

    return $output;
}

The Method

If you create a plugin attached to the OnDocFormRender event, put some HTML in a string, and send it to the modx->event->output() function, MODX will place the HTML at the bottom of the top section on the main tab of the Create/Edit Resource panel (just above the Content section). In the code below, you can see that we've done that at the end of the code, just above the return statement.

This code will display all the fields you might want to see using the IntlDateFormatter class. It gets the raw timestamp directly from the Resource table in the database. If there are some fields you don't want displayed, just comment out or delete the line in the $fields array near the top of the code. Be sure each line in the array ends in a comma. The Tpl used to display each field sets the input section for each field to readonly, because editing these fields directly is a recipe for disaster. That's why MODX doesn't show them. In the code, the attribute is readonly="readonly". If you use just readonly, as is often done, it will work, but the code will not be valid XHTML. To make the fields editable, you need to remove the entire attribute, though MODX may not save them correctly.

The Fields

Here's a quick rundown of the most useful hidden resource fields and what is stored in each one.

  • id — ID of the resource
  • context_key — Name of the resource's context (e.g., web)
  • editedon — The date and time of the last update for the resource
  • createdon — The date and time the resource was created
  • createdby — ID of the user who created the resource
  • editedby — ID of the last user to edit the resource
  • publishedby — ID of the user who published the resource

    There are actually a number of other fields. It's easy to add them to the display, just add a new line for each one to the array near the top of the plugin code below.

The Code

Because the user fields hold user IDs rather than names, we have to get the information from the user with that ID. Our code will have an option to use the username field for this or the fullname field. We'll also make it possible to format the display of the date fields.

Here's the code of the plugin. I called it ExtraDocFields, but you can use any name you like (note that ClassExtender uses ExtraResourceFields, so don't use that). Just paste the code into a new plugin. On the System Events tab, put a check next to OnDocFormRender, and click on the "Save" button.

$output = '';
$fullName = true;

  /* Make it run in either MODX 2 or MODX 3 */
  $prefix = $modx->getVersionData()['version'] >= 3
    ? 'MODX\Revolution\\'
    : '';

/* Note that the Published On (publishedon), Publish Date (pub_date),
   and Unpublish Date (unpub_date) fields are omitted here because
   they are shown in the Manager. You can use a similar technique
   if you want to show them in the front end. */

$fields = array(
    'Resource ID,id,integer',
    'Context,context_key,default',
    'Last Edited On,editedon,date',
    'Created On,createdon,date',
    'Created By,createdby,user',
    'Edited By,editedby,user',
    'Published By,publishedby,user',
);

/* Don't execute for new documents.
   The fields would be bogus except for context_key */

if ($mode == modSystemEvent::MODE_NEW) {
    return '';
}

foreach($fields as $field) {
  $parts = explode(',', $field);
  if (count($parts) != 3) {
    $caption = 'Error: ';
    $value = 'Malformed array member';
    $output .= '<div class="x-form-item x-tab-item">
        <label  class="x-form-item-label" style="color:red">' .
          $caption . ' <span>' . $value .
        '</span></label></div>';
  } else {

    $caption = $parts[0];
    $fieldName = $parts[1];
    $type = $parts[2];
    $value = $resource->get($fieldName);
    $docId = $resource->get('id');

    switch ($type) {
      case 'date':
        $timestamp = getObjectTimestamp(
            $modx,
            $prefix . 'modResource,
            $docId,
            $field
        );

        /* This is the only part that needs editing. You can leave the $pattern
           as is even if you're not using it. If the $method is set to
           'constants' it will be removed. */
        $locale = 'en_us';
        $dateFormat = IntlDateFormatter::MEDIUM;
        $timeFormat = IntlDateformatter::MEDIUM;
        $timezone = 'America/Chicago;;
        $calendar = IntlDateFormatter::GREGORIAN;
        $pattern = 'yMMMMdHms';
        $method = 'constants';

        $value = dateFormatter(
            $timestamp,
            $locale,
            $dateFormat,
            $timeFormat,
            $timezone,
            $calendar,
            $pattern,
            $method,
        );
        break;

      case 'user':
        if ($fullName) {
          $profile = $modx->getObject($prefix . 'modUserProfile',
            array('internalKey' => $value));
          $value = $profile->get('fullname');
        } else {
          $userObject = $modx->getObject($prefix . 'modUser', $value);
          $value = $userObject->get('username');
        }
        break;

      case 'integer':
      default:
        /* Value used as is */
        break;
    }
   $fieldTpl = <<< TPL
    <div class="x-form-item x-tab-item">
        <label for="modx-resource-{$fieldName}" class="x-form-item-label">{$caption}</label>
        <div class="x-form-element" id = "x-form-el-modx-resource-'{$fieldName}" style="padding-left:0;">
             <input type = "text" size="30" readonly="readonly" autocomplete = on"
                    msgtarget="under" id="modx-resource-{$fieldName}
                    name="{$fieldName} "
                    class="x-form-text x-form-field" title=""
                    style = "width: 973px;" value="{$value}">
             <div class="x-form-clear-left"></div>
         </div >
     </div>
TPL;
    $output .= $fieldTpl;

  }
}
// $modx->log(modX::LOG_LEVEL_ERROR, 'Output: ' . $output);

$modx->event->output($output);
return '';

The code starts by initializing the $output variable to an empty string, and setting the $dateFormat and $fullName options. The $dateFormat string will be sent to our dateFormatter() function for formatting. If the $fullName variable is set to false, the username will be used.

Next, we create an array with the fields to show. Each field has three elements, separated by commas. The first one is the caption to be shown for the field, the second one is the field name in the database, the third is the type of field it is. Valid types are date, user, integer, and default. Default fields will be shown exactly as they are retrieved from the DB.

Once we have the $fields array, we loop through it, adding the appropriate stuff to the $output variable as we go. The explode() function returns a PHP array containing the three members of the array element. If any element doesn't have exactly three parts, we add an error message to the output. If the array element is valid, we use the three member of the $parts array to set the $caption, $fieldName, and $type variables. Then, we get the value of the field from the $resource object which is set automatically by MODX when the plugin is called. The value goes in the $value variable, but it may be modified in the switch statement below.

Each section of the switch statement handles a different type of field.

For date fields, we get the raw timestamp using our getObjectTimeStamp() function.

The user fields (createdby, editedby, and publishedby) are a little trickier because the username is in the modUser object and the full name is in the modUserProfile object. Depending on the value of the $fullName variable, we get the appropriate object. Notice that the user object is retrieved directly with the user ID. The user profile object, though, is retrieved by looking for that value in the internalKey field. If you get a profile using the user's ID, it will usually work, but not always. In the profile table, the user's ID is held in the internalKey field and the id field is arbitrary. Since users and their profiles are almost always created at the same time, the two are often the same, but over time, they can diverge.

Integer and default fields are not modified. They're used just as they come from the DB. The 'integer' and 'default' cases in the switch statement are not actually necessary, but they make the code easier to understand.

Finally, we use our Tpl to create the output for the field being processed and add it to the $output variable. Notice that this happens inside the loop, but after the switch statement when the $value is in the form we want.

The Tpl is in syntax. I find it much easier to use this form with complex HTML strings. In that syntax, all single and double quotes are preserved as is, and any variable names are replaced with the values of those variables. Another way to go would have been to create an actual Tpl chunk in the Manager with placeholders for the variables, but that would have meant creating an extra array of elements with the placeholder name as the key and the placeholder value as the value. Doing it as we have avoids the overhead of calling $modx->getChunk() and having MODX parse the chunk and replace all the placeholders each time through the loop.

Finally, we call $modx->event->output() with our string of HTML ($output) as the argument.

Other Formats

You may not like the formatted output produced by the date() function. In that case, you can simply change the $dateFormat variable to modify the outout. For example, you might want to include the time the Resource was created, edited, or published. To do that, you can add g:i a to the end of the formatting string to see something like this:

Dec 23, 2025 4:07:10 pm

For more information on the formatting codes, see this table in the PHP manual.

Styling the Display

The current code mimics the format of the standard fields of the Create/Edit Resource panel, but it pushes the content section down (depending on how many of the fields you show). On some displays, it could be off the screen. It's certainly possible to reformat the display. The values could be shown on the same line as the captions. In fact, the captions and values could all be shown on the same line. The easiest and safest way to reformat the extra field displays would be to add or modify inline styles for the elements to the Tpl code without changing the existing MODX classes. You could, for example, add something like style="display:inline; margin:5px 15px;" in the Tpl code to make everything appear on the same line. Any inline styles you add this way will override the MODX CSS.

Moving the Hidden Field Display

In the Tpl chunk used to display the fields, I've used the MODX standard IDs for each field. I haven't tested it, but I think it might be possible to move the fields around with Form Customization. If that works, the extra fields could go on the Settings tab, or on another existing tab. In theory, you could create another tab and put them there. Doing this is beyond the scope of this article, but feel free to attempt it.

Coming Up

If you're going to the trouble of using the IntDateFormatter, you've probably noticed that the code above isn't quite what you want. You don't really want to set the locale manually in the code. You want to set it to the appropriate value for your visitor. You might want to do the same thing with the timezone. We'll look both topics in my next article.


About Bob Ray

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.

Learn more about Bob Ray.