Extending DOCman with Claude Code: Two Examples

In DOCman Is Built for the AI Era we argued that DOCman's consistent architecture makes it easy for AI agents to extend. Here's the proof: two features, each built by Claude Code from a single prompt on a Joomla 6 site running DOCman 6.2.

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:

  1. Read DOCman's custom fields code and found that DOCman stores field values through Joomla's custom fields system, under the com_docman.document context.
  2. 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.
  3. Wrote the plugin: six files, about 100 lines in total, most of it boilerplate.
  4. 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:

Creating a License field with the new Creative Commons License type in DOCman

Editors get a dropdown in the document form:

The License field in the DOCman document form

And visitors see the license on the document page:

The license displayed on the DOCman 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:

  1. Added a Review by date field in DOCman's field manager. No code needed.
  2. 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.
  3. Wrote one small plugin with three parts:
    • a model behavior that adds an overdue filter 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.
  4. Added the badge with a six-line template override.
  5. 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:

DOCman document list filtered to overdue documents, each with an Overdue badge

And when the scheduler ran, the owners got their reminders (caught here by a local test mail server):

Two "Time to review" reminder emails in the test inbox

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!

Get started with DOCman