Choosing Between Template Variables, MIGX TVs, and Custom xPDO Tables in MODX Revolution
Guide comparing TVs, MIGX TVs, and custom xPDO tables for storing repeatable data in MODX, with step‑by‑step implementation of a custom events table.
27 Oct 2025, 15:54 UTC

Decision and constraints
You need to store structured, repeatable data (for example, a list of events or products) in MODX Revolution. The solution must work with MODX 2.6+ or 3.x, avoid core hacks, support front‑end filtering and sorting, and be manageable from the Manager without writing extensive custom code.
Comparison of supported options
| Option | How it works | Strengths | Weaknesses |
|---|---|---|---|
| Template Variable (TV) on a Resource | Flat key‑value pair attached to a resource; edited via the TV input type. | Fastest to set up; no extra packages; UI‑driven. | Only one value per resource; difficult to query or report on; data stored as serialized strings in the modx_site_tmplvar_contentvalues table, which limits indexing. |
| MIGX TV | Extends a TV with a grid/LTV (list‑TV) that can store repeatable groups of fields. | Adds repeatability without leaving the TV system; still editable in the Manager. | Still stores data as a JSON blob in a single TV value, so complex filtering requires PHP parsing; limited SQL‑based reporting. |
| Custom xPDO table | Defines a standalone database table with an xPDO model; accessed via $modx->getObject or $modx->getQuery. | Full relational schema; proper indexes; powerful queries; data integrity; scalable for large datasets. | Requires creating a package, writing schema XML, generating model and SQL, and registering a namespace; steeper learning curve. |
Trade‑off summary
- Speed of implementation: TV → MIGX TV → Custom table.
- Query performance: Custom table ≫ MIGX TV ≈ TV (both suffer from serialized storage).
- Extensibility: Custom table allows relationships, custom indexes, and future columns without breaking existing data.
- Manager usability: TV and MIGX TV are instantly available; a custom table needs a simple component grid (can be built with MIGX or custom controller).
If your data is simple, flat, and rarely queried, a TV or MIGX TV may suffice. For repeatable, searchable, or reporting‑heavy data, a custom xPDO table is the recommended path.
Implementing a custom xPDO table – step‑by‑step
The following example creates a package called events that stores event records (name, date, location). Adjust the placeholders to suit your component.
1. Create the package structure
Under core/components/ make a directory for the package:
mkdir -p core/components/events/{model,processors}
Set file system permissions so the web‑server user can write to these directories (typically www-data:www-data on Linux).
2. Write the schema XML
Create core/components/events/model/schema/events.mysql.schema.xml with the following content:
<model package="events" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM">
<object class="Event" table="events_event" extends="xPDOSimpleObject">
<field key="name" dbtype="varchar" precision="255" attributes="null" default="" />
<field key="event_date" dbtype="datetime" attributes="null" />
<field key="location" dbtype="varchar" precision="255" attributes="null" default="" />
<index alias="name">
<column name="name" length="255"/>
</index>
<index alias="date">
<column name="event_date" />
</index>
</object>
</model>
This defines an Event object with columns id (auto‑added by xPDOSimpleObject), name, event_date, and location, plus indexes on name and date for fast look‑ups.
3. Generate the model and SQL
Place the generator scripts in core/components/events/_build/ (you can copy them from any existing MODX extra). Then run:
# From the package root
php _build/schema.build.map.php
php _build/schema.build.sql.php
These commands:
- Create PHP class files under
core/components/events/model/. - Produce
core/components/events/sql/install.sqlcontaining theCREATE TABLEstatement.
Required permission: the executing user must be able to read the schema XML and write to the model and sql directories.
4. Install the tables
In MODX Manager:
- Navigate to Packages → Acknowledge Installation.
- Browse to the package directory (
core/components/events/) and acknowledge. - Go to Packages → Installer, click Create Package, fill in:
- Package name:
events - Workspace:
core - Click Create.
- Right‑click the new package and choose Install.
The installer runs the generated SQL, creating the table modx_events_event (prefix depends on your table_prefix setting).
5. Register a namespace (optional but recommended)
Go to System → Namespaces → New and add:
- Name:
events - Core Path:
{core_path}components/events/ - Assets Path:
{assets_path}components/events/
This lets you reference the package in snippets without hard‑coding paths.
6. Use the model in a snippet
Create a snippet (e.g., [[!UpcomingEvents]]) with:
<?php
$today = strtotime('today');
$criteria = array(
'event_date:>' => $today,
);
$events = $modx->getObjectCollection('Event', $criteria, array('sort' => 'event_date', 'dir' => 'ASC'));
$output = '';
foreach ($events as $e) {
$output .= '<li>' . $e->get('name') . ' – ' . $e->get('event_date') . ' – ' . $e->get('location') . '</li>';
}
return !empty($output) ? '<ul>' . $output . '</ul>' : '<p>No upcoming events.</p>;
?>
Place the snippet call in a resource or template to see a list of upcoming events.
Validation and practical checks
- After installation, open Components → Events in the Manager. You should see a grid with columns Name, Date, Location.
- Add a test record via the grid’s “Create” button.
- Run a snippet that returns the count:
<?php return $modx->getObjectCount('Event'); ?>
Compare the numeric output to the number of rows shown in the Manager grid; they should match.
modx_events_event exists with columns id, name, event_date, location.Limitations
- Initial setup requires familiarity with XML schema, xPDO, and MODX packaging.
- Future schema changes need migration scripts; MODX does not provide automatic schema versioning out of the box.
- If you only need a single flat value per resource, the overhead of a custom table may be unnecessary.
Rollback (if needed)
Because installing the package creates database tables and files, you can revert the changes:
- In the Manager, Packages → Installer, locate the
eventspackage and click Uninstall. This drops themodx_events_eventtable. - Delete the namespace under System → Namespaces.
- Remove the directory
core/components/events/and any assets you added.
Perform these steps only after backing up your database, as they permanently delete the stored event data.
Conclusion
For simple, non‑repeating data, Template Variables are the quickest route. MIGX TVs add repeatability but still suffer from serialized storage. When you need query‑friendly, extensible, and performant storage for structured lists, a custom xPDO table—though it requires more initial work—provides the best long‑term foundation within MODX Revolution.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.