Example 1: a new field type
We asked:
Add a Creative Commons license field to DOCman documents. Editors should pick a license from a dropdown, and visitors should see a link to the license on the document page.
Here's what it did, in order:
- Read DOCman's custom fields code and found that DOCman stores field values through Joomla's custom fields system, under the
com_docman.documentcontext. - Concluded the right tool was a standard Joomla field plugin, not a change to DOCman, and used DOCman's own field plugins as a reference.
- Wrote the plugin: six files, about 100 lines in total, most of it boilerplate.
- Installed it with Joomla's command-line tool, created a License field in DOCman's field manager, set a license on a document, and loaded the page to check the result.
The interesting part is only a few lines. The plugin lists the licenses and turns the field into a dropdown:
final class Cclicense extends FieldsPlugin
{
public const LICENSES = [
'cc0' => 'CC0 1.0',
'by' => 'CC BY 4.0',
'by-sa' => 'CC BY-SA 4.0',
// ...and the rest of the CC 4.0 family
];
public function onCustomFieldsPrepareDom($field, $parent, $form)
{
$node = parent::onCustomFieldsPrepareDom($field, $parent, $form);
if (!$node) {
return $node;
}
$node->setAttribute('type', 'list');
foreach (self::LICENSES as $value => $label) {
$option = $node->appendChild(new \DOMElement('option', $label));
$option->setAttribute('value', $value);
}
return $node;
}
}
The new type shows up in DOCman's field manager like any built-in one:
Editors get a dropdown in the document form:
And visitors see the license on the document page:
To be fair about it: it wasn't completely hands-off. Our test site's menu item linked document titles straight to the file download, so there was no document page to check at first. Claude Code spotted that and switched the menu item to show document details. That's a site setting, not a code problem, and it's the kind of thing you'd hit doing this by hand too.
Example 2: a feature across every layer
A new field is the simple case. So we asked for something that cuts across the whole system:
Add a "review by" date to documents. Email the owner when a document is overdue, push the date a year out whenever the document is updated, let visitors filter the list to overdue documents, and show an "Overdue" badge.
Here's what it did:
- Added a Review by date field in DOCman's field manager. No code needed.
- Read how DOCman's own expiry notifications work, since they already combine a filter, a scheduled job and a plugin, and followed the same shape.
- Wrote one small plugin with three parts:
- a model behavior that adds an
overduefilter to every document list, - an event subscriber that pushes the review date out when a document is saved,
- a scheduled job that emails owners once a day while a document is overdue.
- a model behavior that adds an
- Added the badge with a six-line template override.
- Tested every part: the badge, the filter, saving a document, and running the scheduler.
The filter is a short model behavior. It registers a new overdue state and, when it's set, joins the review dates into DOCman's document query:
public function onMixin(KObjectMixable $mixer)
{
parent::onMixin($mixer);
$mixer->getState()->insert('overdue', 'boolean', false);
}
protected function _beforeFetch(KModelContextInterface $context)
{
$state = $context->state;
if (!$state->isUnique() && $state->overdue)
{
$context->query
->join(['_rv' => 'fields_values'], '_rv.item_id = tbl.docman_document_id')
->join(['_rf' => 'fields'], '_rf.id = _rv.field_id')
->where('_rf.context = :review_context')
->where('_rf.name = :review_name')
->where('_rv.value < :review_today')
->bind([
'review_context' => 'com_docman.document',
'review_name' => PlgKoowaDocmanreview::FIELD,
'review_today' => gmdate('Y-m-d'),
]);
}
}
The event subscriber is the part we liked most. A plain "always push the date" would overwrite a date the editor had just picked, so Claude Code made it remember the date before the save and only move it if the editor left it alone:
public function onBeforeDocmanDocumentControllerEdit($event)
{
foreach ($event->getTarget()->getModel()->fetch() as $document) {
$this->_dates[$document->id] = $this->_getReviewDate($document->id);
}
}
public function onAfterDocmanDocumentControllerEdit($event)
{
foreach ($event->result as $document)
{
$before = $this->_dates[$document->id] ?? null;
// Only push the date out if the editor didn't pick a new one
if ($before && $this->_getReviewDate($document->id) === $before)
{
$this->_setReviewDate($document, gmdate('Y-m-d', strtotime('+1 year')));
}
}
}
Visitors can now list just the overdue documents with ?overdue=1, each with its badge:
And when the scheduler ran, the owners got their reminders (caught here by a local test mail server):
Testing caught two bugs, and Claude Code fixed both itself. Classes in a koowa plugin can't be autoloaded, so it loads them explicitly. And Joomla's date field saves dates without a time, so it compares dates only.
It also reported one limit instead of hacking around it. DOCman's category pages build their document list from a fixed set of filters, so ?overdue=1 works on the flat document list and the JSON API, but not on category pages. Supporting those would mean overriding that view, and it left that call to us.
What neither example did
Neither example touched a single DOCman file. Both are plugins and overrides that sit next to DOCman and use the extension points it already provides, so nothing breaks when you update DOCman. And because they follow DOCman's and Joomla's own patterns, any developer can pick them up and maintain them.
That's the point of the first post: you don't need a special AI edition of DOCman. Describe what you need, and let your agent find the right place to plug it in. Our developer documentation and plugin guide are a good place for you and your agent to start.
Get started
Try the latest DOCman on our demo or download it from your Joomlatools Dashboard. Not yet a member? Get a subscription and start building today!
