Skip to content

phpClaw for Joomla

The Joomla adapter is one package with three extensions: the com_phpclaw component (chat and admin pages), the plg_system_phpclaw plugin (settings), and the plg_webservices_phpclaw plugin (API routes). It adds two console commands and 14 tools.


RequirementVersion
PHP8.1 or later
Joomla4, 5 or 6

Download phpClaw for Joomla (.zip)

In the Joomla admin, go to System → Install → Extensions → Upload Package File and upload the ZIP. Installing creates #__phpclaw_conversations, #__phpclaw_messages and #__phpclaw_memory; the agent also creates them when it starts if they are missing.

Then open System → Plugins → System - phpClaw, enable it, choose a provider and enter the API key.

Need a specific version? Browse all releases →

phpClaw in a Joomla site


Settings are stored in the System - phpClaw plugin’s parameters:

FieldNotes
Provider, Modelthe provider to use
API Keyhidden for Ollama
Base URLshown for the Custom provider; https://, or http:// on localhost, 127.0.0.1 or ::1
Store MessagesYes by default
System Promptcut to 8000 characters
Max Iterationsdefault 20
Remote Skill URLsone HTTPS URL per line ending in .md or .json; at most 50 are loaded
Cloud Key, Cloud Signing Secret, Cloud Disableshown while Store Messages is Yes; configure phpClaw Cloud. Cloud Disable accepts scan, observability, and the hide_inputs, hide_outputs, hide_metadata names that keep content on your server (Cloud)

Cloud boots only while Store Messages is on and a Cloud Key is set. With Store Messages off, the agent stores no message content. See Memory.

The plugin parameters also accept shell_allowlist and tool_deny as comma-separated strings, and guards, hooks and skills as JSON. They have no form field. An empty shell_allowlist uses core’s default allowlist.


The component defines two ACL actions, set under System → Global Configuration → phpClaw → Permissions or per user group:

Action on com_phpclawAllows
phpclaw.chat.usethe Chat, Analytics, Guide and About pages, both API routes, and every Joomla tool
phpclaw.chat.manageallseeing every user’s conversations and counts on Chat and Analytics

Saving settings, enabling the plugin and Test Connection need Joomla’s core.admin.

A Joomla tool call without the action returns a FORBIDDEN error. When the Joomla console application runs the agent, the check is skipped. A script that boots Joomla some other way is still checked: the test is instanceof ConsoleApplication, not PHP_SAPI === 'cli'.


14 tools:

GroupTools
Joomlajoomla_articles, joomla_users, joomla_categories, joomla_extensions, joomla_database_query
Coreshell_exec, http_request, file_read, file_write, file_edit, code_search, project_info
Packagingzip_package, joomla_zip_extension
ToolDoesExample question
joomla_articleslists and searches articlesshow me the latest articles on the site
joomla_categorieslists and searches categorieslist the categories on this site
joomla_extensionslists and searches installed extensionswhat extensions are installed
joomla_userslists and searches registered userslist the registered users
joomla_database_queryruns one read-only SELECTshow me the five most recently modified articles
joomla_zip_extensionvalidates a generated extension and packages it as a ZIPvalidate and package the extension I just generated

The example questions come from each tool’s own examples.

The file tools work inside administrator/components/com_phpclaw/storage/workspace. See Tools.

Tools added through onPhpClawExtraTools are placed before the built-in tools.

Every agent is built with CliApprovalGate. In a terminal you are asked before a tool changes something; in the chat page and the API there is nobody to ask, so the change is refused. See Security: approval. The MCP server does not use the approval gate.

file_write may create .php, .phtml and .phar files only when PHP runs from the command line, and never from the MCP server, whose command builds its tools with allowPhpWrite: false.

GroupTools
group:contentjoomla_articles, joomla_categories
group:adminjoomla_users, joomla_extensions
group:systemjoomla_database_query, joomla_zip_extension, zip_package, file_read, file_write, file_edit, code_search, project_info, shell_exec, http_request

The five Joomla data tools and the extension packager answer in one envelope:

{
"success": true,
"data": { "categories": [{ "id": 2, "title": "Uncategorised" }] },
"meta": { "mode": "query", "total": 9, "count": 2, "has_more": true, "next_offset": 2 },
"warnings": []
}

On failure success is false, error holds a code and message, data is null and meta.mode is "error". The core tools return their own formats.

The four collection tools (joomla_articles, joomla_categories, joomla_extensions and joomla_users) accept {"schema": true} to describe their fields without reading rows, and {"aggregate": true} for counts only. Setting both returns CONFLICTING_MODES. Aggregate mode warns IGNORED_ARGUMENT for columns, limit, offset, order_by and order_dir. When meta.has_more is true, send meta.next_offset back as offset.

meta.total is a COUNT(*) over the same filter as the page, and rows with the same sort value are ordered by the primary key, so pages stay stable. meta.columns_returned names the fields sent.

Error codeWhen
FORBIDDENthe caller lacks phpclaw.chat.use
UNKNOWN_COLUMNa field that does not exist was asked for; the error lists available_columns
BLOCKED_COLUMNa refused field was asked for
BLOCKED_IDENTIFIERa query named a restricted column such as password
BLOCKED_STATEMENTthe SQL was not a single read
INVALID_SQLthe database rejected the query; its message is passed back
CONFLICTING_MODESschema and aggregate were both set

Fields that are never returned:

ToolRefused fields
joomla_userspassword, otpKey, otep, activation, params, token, secret
joomla_articlesfulltext, images, urls, attribs, metadata, metakey, metadesc
joomla_categoriesparams, metadata, metadesc, metakey, note
joomla_extensionsparams, custom_data, checked_out, checked_out_time

The first three tools answer a request for one of those fields with BLOCKED_COLUMN. joomla_extensions lists its four under blocked_columns in its schema but answers with UNKNOWN_COLUMN.

CodeMeaning
UNTRUSTED_CONTENTthe response contains text written by users or by extension authors
SENSITIVE_DATApersonal data was returned
IGNORED_ARGUMENTan argument did not apply to the mode used
OUTPUT_TRUNCATEDjoomla_database_query output passed its 8192-byte limit; meta then carries truncated, bytes_kept, bytes_produced, and rows_matched

UNTRUSTED_CONTENT comes with meta.untrusted_fields_returned. On joomla_extensions it covers the name, version, author and description copied from each extension’s manifest. Only those three manifest values are taken; the rest of the stored manifest is dropped, and schema lists them as untrusted_manifest_fields. joomla_articles uses stronger wording when you ask for unpublished (0), trashed (-2) or archived (2) articles. On joomla_users, name and username are untrusted and email, username and lastvisitDate are sensitive, so a returned username carries both warnings.

joomla_database_query runs one read-only SELECT. It refuses the session, extensions, phpclaw_conversations and phpclaw_messages tables; for extensions it names joomla_extensions, which reads extension data through an allowlist.


The extension_scaffolder skill adds Joomla extension rules to the message when words such as scaffold, manifest, boilerplate, generate or installable appear. After the agent writes the files, joomla_zip_extension checks before packaging:

  • a manifest declares <extension type="plugin">, component or module, and every file and folder it lists is present
  • PHP files carry the _JEXEC guard
  • no file calls eval(, system(, exec(, passthru( or shell_exec(
  • every PHP file passes a syntax check
  • a plugin is wired the Joomla 4, 5 and 6 way: <filename plugin="..."> equals the folder name, the manifest declares <namespace path="src">, services/provider.php registers a factory under Joomla\CMS\Extension\PluginInterface and sets the application and dispatcher, and every class under src/ sits where its namespace says, with no Joomla 3 constructor

A failed check names the file and the problem. The skill’s rules cover plugins only; for a component or module it tells the agent those are not supported yet. Every write needs approval, so run it from the console:

Terminal window
php cli/joomla.php phpclaw "Generate an installable system plugin that logs each page render, then package it."

Under Components → phpClaw, each page needs phpclaw.chat.use:

PageShows
Chatthe chat interface with the user’s conversations
Analyticsconversation and message counts; site-wide for phpclaw.chat.manageall, otherwise the user’s own
Guidethe registered tools and extensions
Aboutcomponent information

Each conversation stores its creator’s Joomla user id. Conversations created from the console store 0. A user sees and opens only their own conversations; opening another user’s is refused with 403. phpclaw.chat.manageall shows every conversation.

The agent always uses the joomladb memory driver, JoomlaDbRouterMemory: conversations go to JoomlaDbConversationMemory and the two conversation tables, everything else to JoomlaDbMemory and #__phpclaw_memory.


Terminal window
php cli/joomla.php phpclaw "list recent articles"
php cli/joomla.php phpclaw "summarise all articles" --stream
php cli/joomla.php phpclaw "and the week before?" --conv-id=01JQABCDE...
php cli/joomla.php phpclaw:mcp-server

phpclaw takes --stream and --conv-id to continue a conversation. phpclaw:mcp-server serves the tools over stdio; see MCP.


MethodRouteReturns
POSTapi/index.php/v1/phpclaw/chatthe reply as JSON
POSTapi/index.php/v1/phpclaw/chat/streamServer-Sent Events

Both need a Joomla API token and phpclaw.chat.use.

Terminal window
curl -X POST https://example.com/api/index.php/v1/phpclaw/chat \
-H "Authorization: Bearer YOUR_JOOMLA_API_TOKEN" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/json" \
-d '{"message":"how many articles were published today?"}'

Before the routes answer, enable Web Services - phpClaw, API Authentication - Token and User - Joomla API Token, and make sure the calling user’s group may log in to the API and hold a token. Those are Joomla’s own settings: by default Joomla grants API login only to Super Users, and the User - Joomla API Token plugin’s Allowed User Groups decides who may hold a token. Without them Joomla answers 401 before phpClaw runs.

The API carries chat only. Saving settings, enabling the plugin and Test Connection stay on the admin screens, behind an administrator session and Joomla’s core.admin.

The body is JSON with message (at most 40,000 characters) and an optional conversation_id. The response is {"success": true, "data": {...}}, where data holds text, provider, model, tokens, iterations, conversation_id and tool_calls.

StatusWhen
400the message is empty or too long, or a guard blocked it
403the caller lacks phpclaw.chat.use, or the conversation belongs to another user
500anything else failed

chat/stream uses the text/event-stream format and sends error events for failures.


A Joomla plugin can listen for these events, each fired while the agent starts:

EventAdds
onPhpClawExtraToolstools
onPhpClawExtraGuardsguards
onPhpClawExtraHookslifecycle listeners
onPhpClawExtraSkillsskills
onPhpClawExtraMemoryDriversmemory drivers
onPhpClawExtraProvidersproviders

The adapter tries to dispatch every lifecycle event on Joomla’s event dispatcher as onPhpClaw followed by the event name in CamelCase, such as onPhpClawAgentAfter, but no event reaches a Joomla listener. The bridge builds its event with Joomla\Event\GenericEvent, which Joomla 4, 5 and 6 do not ship, and the error is swallowed.

To react to lifecycle events, add a listener through onPhpClawExtraHooks instead:

public function onPhpClawExtraHooks(\Joomla\Event\EventInterface $event): void
{
$hooks = $event->getArgument('hooks') ?? [];
$hooks[] = ['event' => 'agent.after', 'handler' => fn (array $ctx) => $this->log($ctx), 'priority' => 10];
$event->setArgument('hooks', $hooks);
}

The event names are listed on the Hooks page.


The package lists https://phpclaw.ai/api/joomla-update/package as its update server, so updates it publishes appear under System → Update → Extensions. To update by hand, upload the new package ZIP; installing over the existing version keeps the tables and settings.