Important Tips for MODX Extra Developers

MODX logoAs the creator of a MODX extra, you have a responsibility to remove all parts of your package when your package is uninstalled. Some of the things installed by a package are automatically uninstalled, but not all of them.

This article contains some tips about what is and isn’t removed and how to remove the things that might hang around.



The standard package resolver has three separate sections: install, upgrade, and uninstall (though the first two are often combined). Here’s a typical resolver format:

[code language=”php”]
if ($object->xpdo) {
$modx =& $object->xpdo;

switch ($options[xPDOTransport::PACKAGE_ACTION]) {
case xPDOTransport::ACTION_INSTALL:
/* install code here */

break; /* remove this if upgrades get the same code */

case xPDOTransport::ACTION_UPGRADE:
/* Code specific to upgrades (if any) */


/* Uninstall Code */


return true;



When files are installed with a file resolver, they are automatically removed. Some packages, however, create or move files somewhere else. If a directory is specified as a target in a file resolver, you can count on it, and all the files it contains (even if some weren’t there originally), being removed during uninstall. If your package creates files elsewhere (utility PHP files, image files, log files, etc.), you should remove them manually in a resolver.

If you need to remove a directory that was not created in a file resolver (and its contents), here’s a handy function to do that. Just pass it the path to the directory:

[code language=”php”]
/** recursive remove dir function */
function rrmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (filetype($dir . "/" . $object) == "dir") {
$this->rrmdir($dir . "/" . $object);
} else {
unlink($dir . "/" . $object);


If your package creates cron jobs, they should also be removed on uninstall.


System Settings and Configuration

If your package creates new System Settings, some versions of MODX are not very good about removing them. It’s not a bad idea to add a section to your resolver’s uninstall section that checks to see if they exist and removes them if they do.

If your package modifies existing System Settings, it’s good form to save the original values and restore them on uninstall. Saving and restoring the values is best done in a validator that saves the original values to a file, rather than a resolver. If you save the values in a resolver, the file with the original values may be gone by the time your resolver runs. The format for a validator is exactly the same as for a resolver (with the same three sections).

It’s critical to restore the settings if your package changes settings that MODX depends on. If, for example, your package changes the default Manager theme to your own and you remove your own theme on uninstall, the user may be left with a non-functioning Manager when it tries to load your theme!

In the resolver or validator that sets the values, get the current values first and save them to a file. Saving the values is usually best done with a validator that runs at the very beginning of your install process, rather than a resolver.



Sometimes you don’t want a package to overwrite things the user might have changed. Say, for example, that you install a few resources. You expect the user to modify them. It’s tempting to use xPDOTransport::UPDATE_OBJECT => false for those objects in your build.transport.php file so that the user won’t lose their work if they update or re-install the package. The problem with this is that the resources will not be removed on uninstall. I’ve filed a feature request to separate the update and removal aspects of this setting in transport packages, but as of this writing, any object with this attribute set to false will not be removed. If you ever do this, you need remove them manually.

If your package creates chunks, for example, and UPDATE_OBJECT is set to false for them, you should do something like this in the uninstall section:

[code language=”php”]
$chunks = array(

foreach($chunks as $chunkName) {
$obj = $modx->getObject(‘modChunk’, array(‘name’ => $chunkName));
if ($obj) {
$cm = $modx->getCacheManager();


It’s also good form to warn the user if you can’t find the object and suspect that it’s still there. You might, for example, create a resource in a resolver and try to remove it based on its pagetitle. If the user has changed the pagetitle, though, you won’t find it. Here’s an example:

[code language=”php”]
$doc = $modx->getObject(‘modResource’, array(‘pagetitle’ => ‘MyResource’));
if (!empty($doc)) {
} else {
$modx->log(modX::LOG_LEVEL_ERROR, ‘Could not find MyResource: remove manually’);



Registered Extension Packages

If your package registers itself (i.e., adds a reference in the extension_packages System Setting or creates a modExtensionPackage object), either in a resolver’s install section or when its code executes later on, you need to remove that reference or it will persist and potentially cause trouble for the user. Packages are not automatically un-registered on uninstall.

Here’s an example from the uninstall section for ClassExtender, which may (or may not) have registered the extendeduser or extendedresource packages.

[code language=”php”]
/* Remove package in MODX < 2.3 */
$setting = $modx->getObject(‘modSystemSetting’,
array(‘key’ => ‘extension_packages’));

/* make sure the setting exists */
if (! empty($setting)) {

/* Remove modExtensionPackage objects in 2.3 + */
if (class_exists(‘modExtensionPackage’)) {
/** @var $rec xPDOObject */
$recs = $modx->getCollection(‘modExtensionPackage’,
array(‘namespace’ => ‘extendeduser’));
foreach ($recs as $rec) {
$recs = $modx->getCollection(‘modExtensionPackage’,
array(‘namespace’ => ‘extendedresource’));
foreach ($recs as $rec) {


Sanity Checks

Any time you remove an object in a resolver, *always* check to see if it exists first. Otherwise, calling remove() on it will crash your uninstall and potentially leave lots of pieces of your package behind. Worse yet, the remaining parts may still execute and crash MODX when they try to access the parts that are gone.

Be careful, getObject() returns null on failure and getCollection() returns an empty array. The safe test for both is if(empty($object)) {}.


Sequence Issues

The order of events in your package is important for all phases of the process (install, upgrade, and uninstall).

When your uninstall removes things manually in a resolver or validator, think carefully about the sequence of events. If you need to find the children of a resource or category, for example, be sure you do that before the parent is removed.

Here’s a quick summary that may help you understand the order of events in Transport Packages.

Each transport package is made up of vehicles (there can be as many as you need). Validators and Resolvers are attached to specific vehicles. The vehicles carry the objects (chunks, snippets, plugins, etc.) and the resolvers and validators that will run when the package is installed updated, or uninstalled.

Resolvers and Validators are just bits of code that run during the process. There’s really no difference between them except the time that they execute (though validators give you the option to abort the install).

  • Each vehicle is processed in the order it was added to the package in build.transport.php.
  • Validators attached to a vehicle execute just before the objects in the vehicle are processed.
  • Resolvers attached to a vehicle execute just after the objects in the vehicle are processed.


For more information on how to use MODX to create a web site, see my web site Bob’s Guides, or
better yet, buy my book: MODX: The Official Guide.

Looking for quality MODX Web Hosting? Look no further than Arvixe Web Hosting!

Tags: , , , , | Posted under MODX, MODX | RSS 2.0

Author Spotlight

Bob Ray

Bob Ray is the author of MODX: The Official Guide and over 30 MODX add-on components. He hosts Bob's Guides, a source of valuable information for MODX users, and has been very active in the MODX Forums with over 19,000 posts.

Leave a Reply

Your email address will not be published. Required fields are marked *