
AJAX in Joomla
Most of Joomla is built to return a whole web page. But sometimes you only want a small piece of data: a live search result, a "snooze this notice" click, a saved setting, a price that updates without a reload. For that, Joomla ships a tiny but powerful component called com_ajax.
This article explains how Joomla's Ajax component really works. It starts with the basics for site owners, moves on to the setup for administrators, and then digs into the technical details for developers who want to build their own Ajax endpoints.
One small component that lets any extension answer a request without building a controller.
The goal is simple: help you understand com_ajax well enough to call it, secure it, and build on it with confidence.
1. The Basics
1.1 What is com_ajax?
The name "Ajax" stands for an old idea: a page asks the server for a small piece of data in the background, then updates itself without a full reload. com_ajax is the core component that makes this easy in Joomla.
It is unusual among Joomla components because it has almost no code of its own. It does not build a page, it has no menu items, and it has no views. Its only job is to take an incoming request, hand it to the right extension, and return whatever that extension produces.
Think of com_ajax as a switchboard operator: it does not answer your question itself, it just connects your call to the right extension and hands the answer back.
1.2 The Problem It Solves
Before com_ajax, every developer who wanted a small JSON response had to build a full component with a controller, a model, and a router, just to return one value. That is a lot of work for one number.
com_ajax removes that work. A plugin, a module, or a template can expose a single method, and com_ajax will call it. You write the logic, Joomla handles the request, the routing, and the response format.
Once you have it, a lot of common features become easy:
- Live search and search suggestions as the user types.
- Dependent fields - pick a country, and the list of states updates.
- Lazy loading - load heavy content only when it scrolls into view.
- Save without reload - a setting, a vote, a "snooze this notice" click.
- Dashboard widgets that each fetch their own data.
1.3 The URL Anatomy
Every call to com_ajax is just a URL with a few query parameters. Here is the shape of a typical request:
index.php?option=com_ajax&plugin=hello&group=ajax&format=json
| | | |
| | | └─ response format (required)
| | └─ which plugin group to load
| └─ which target to call
└─ always com_ajax
| Parameter | Meaning |
|---|---|
option=com_ajax |
Always present. Routes the request to the Ajax component. |
plugin / module / template |
Which kind of extension answers the call (pick one). |
format |
The response format: json, raw, or debug. Required. |
method |
For modules and templates: which helper method to call (default get). |
group |
For plugins: the plugin group to load (default ajax). |
The same component is reachable from the front end (index.php) and from the back end (administrator/index.php). The back-end version simply includes the front-end file.
2. The Three Targets
com_ajax can route a request to one of three extension types. It checks them in a fixed order and uses the first one it finds in the URL.
2.1 Plugin, Module, or Template
| URL parameter | What gets called | Best for |
|---|---|---|
plugin=hello |
The event onAjaxHello in the chosen plugin group |
Reusable, site-wide logic. The most common choice. |
module=foo |
ModFooHelper::getAjax() in mod_foo |
Logic that belongs to one module (live search, load more). |
template=bar |
TplBarHelper::getAjax() in the bar template |
Logic tied to one template (theme switch, layout option). |
You only set one of these three. If you set more than one, com_ajax handles the plugin first, then module, then template, in that order.
2.2 The format Parameter Is Required
com_ajax refuses to guess how to format the answer. If you leave out format, you get a 404 error with the message "Please specify a valid response format". This is on purpose: an Ajax endpoint should never return a full HTML page by accident.
format=jsonwraps the result in a standard JSON envelope.format=rawreturns the result as plain text or HTML, with no wrapper.format=debugis handy while developing, because it shows errors more openly.
Section 6 covers the formats in detail.
Back to top3. Plugins: the Most Common Target
For most projects, an Ajax plugin is the right tool. A plugin is not tied to a single module or template, so you can call it from anywhere on the site.
3.1 The onAjax<Name> Event
When you call index.php?option=com_ajax&plugin=hello&format=json, com_ajax does two things:
- It imports every plugin in the requested group (default
ajax; override it with&group=systemand similar). - It fires the event
onAjaxHello- the word afterplugin=, capitalised, withonAjaxin front.
plugin=hello ──→ event onAjaxHello
plugin=stats ──→ event onAjaxStats
plugin=cart ──→ event onAjaxCart
Any enabled plugin that subscribes to that event will run. This is the same publish-and-subscribe model that drives all Joomla plugins.
3.2 The AjaxEvent Object
Since Joomla 5, com_ajax passes a typed event object, AjaxEvent, to your method. The event always carries the application as its subject, and it collects whatever you return in a result argument.
// Inside com_ajax, simplified:
$eventName = 'onAjax' . ucfirst($input->get('plugin', ''));
$results = $dispatcher
->dispatch($eventName, new AjaxEvent($eventName, ['subject' => $app]))
->getArgument('result', []);
Your plugin reads the request, does its work, and adds its answer to the event's result. com_ajax then formats that result and sends it back.
3.3 A Complete Example Plugin
Here is a minimal, modern Ajax plugin. It lives in plugins/ajax/hello/ and answers with a greeting:
use Joomla\CMS\Event\Plugin\AjaxEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
final class Hello extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['onAjaxHello' => 'onAjaxHello'];
}
public function onAjaxHello(AjaxEvent $event): void
{
$app = $event->getApplication();
$name = $app->getInput()->getString('name', 'world');
// Add the answer to the event result:
$event->addResult(['greeting' => 'Hello, ' . $name . '!']);
}
}
Enable the plugin, then visit:
index.php?option=com_ajax&plugin=hello&format=json&name=JUG
You get back a JSON envelope whose data contains your greeting.
3.4 Returning the Result
The AjaxEvent object gives you three clear ways to set the answer:
| Method | What it does |
|---|---|
$event->addResult($data) |
Appends to the result (several plugins can each add a piece). |
$event->updateEventResult($data) |
Replaces the whole result with your value. |
$event->setArgument('result', $data) |
The low-level way; both methods above build on it. |
Use addResult() when more than one plugin may answer the same event, and updateEventResult() when your plugin owns the whole response.
4. Modules and Templates as Targets
Plugins are flexible, but sometimes the logic clearly belongs to one module or one template. com_ajax supports both, using a simple helper-method convention.
4.1 Module Helpers
When you call index.php?option=com_ajax&module=foo&format=json, com_ajax looks for the helper of mod_foo and calls a method built from the method parameter plus the word Ajax:
method=get (default) ──→ getAjax()
method=save ──→ saveAjax()
method=search ──→ searchAjax()
// modules/mod_foo/src/Helper/FooHelper.php (modern helper-factory style)
namespace Joomla\Module\Foo\Site\Helper;
class FooHelper
{
public function getAjax(): array
{
// ... do the work, return data ...
return ['time' => date('H:i:s')];
}
}
Two conditions must be true or you get a 404:
- The module must be enabled. com_ajax checks the
#__extensionstable first. - The helper method must exist and end in
Ajax.
Older modules expose the method as a static call (ModFooHelper::getAjax() in modules/mod_foo/helper.php). com_ajax supports both the modern helper factory and the classic static helper.
4.2 Template Helpers
Templates work the same way, with template=bar and a TplBarHelper class in templates/bar/helper.php:
index.php?option=com_ajax&template=bar&method=get&format=json
──→ TplBarHelper::getAjax()
The template must be installed and enabled. This is useful for theme settings, such as a dark-mode toggle that needs to save a choice without reloading the page.
Back to top5. Calling com_ajax from the Browser
So far we have looked at the server side. Now let us call the endpoint from JavaScript, which is where com_ajax is normally used.
5.1 A Simple Fetch Call
The modern way is the browser's built-in fetch. Core Joomla plugins use exactly this pattern:
const url = 'index.php?option=com_ajax&plugin=hello&format=json&name=JUG';
fetch(url, { method: 'GET' })
.then((response) => response.json())
.then((data) => {
console.log(data.data); // your result lives in .data
});
Joomla also ships a small helper, Joomla.request(), which adds the security token automatically. You can read its options with Joomla.getOptions('system.paths') to build a correct root URL.
5.2 The CSRF Token and POST Requests
A read-only GET request is fine for fetching data. But any call that changes something - saving, deleting, sending - must protect against cross-site request forgery (CSRF). Joomla expects a one-time token.
const token = Joomla.getOptions('csrf.token', '');
fetch('index.php?option=com_ajax&plugin=cart&format=json', {
method: 'POST',
headers: { 'X-CSRF-Token': token },
body: new URLSearchParams({ productId: 42 }),
});
On the server, check the token with Session::checkToken() before you act on a write request. Never trust a GET request to change data.
5.3 Passing Parameters
Anything you add to the URL or the POST body is available through the application input, exactly like in any Joomla code:
$input = $event->getApplication()->getInput();
$id = $input->getInt('id'); // safe integer
$name = $input->getString('name'); // filtered string
$html = $input->get('body', '', 'raw'); // raw, use with care
Always use the typed getters (getInt, getString, getWord). They filter the input for you and prevent many injection problems.
5.4 Check Permissions on the Server
Filtering input is not the same as checking who is allowed to do something. A front-end endpoint can be called by anyone, so your method must verify the user's rights itself, with Joomla's ACL:
$user = $event->getApplication()->getIdentity();
if (!$user->authorise('core.edit', 'com_inventory')) {
throw new \RuntimeException('Not allowed', 403);
}
Never rely on the URL or a hidden form field to decide access. The rule is simple: filter every input, and authorise every action.
Back to top6. Response Formats
The format parameter decides how com_ajax wraps your result before sending it. Choosing the right format matters for both the browser and your error handling.
6.1 Raw Format
With format=raw, com_ajax outputs your result as-is: a string, a number, or HTML, with no wrapper. This is the right choice when you return a fragment of HTML to drop straight into the page.
index.php?option=com_ajax&module=foo&format=raw
──→ <ul><li>Result 1</li><li>Result 2</li></ul>
6.2 JSON Format and the Response Envelope
With format=json, com_ajax wraps your result in a JsonResponse. This envelope has a fixed, predictable shape:
{
"success": true,
"message": null,
"messages": null,
"data": { "greeting": "Hello, JUG!" }
}
| Field | Meaning |
|---|---|
success |
true normally, false if the result was an exception. |
message |
An optional main message. |
messages |
Joomla's enqueued system messages (warnings, notices). |
data |
Your actual result. This is what you read in JavaScript. |
By default, com_ajax sets ignoreMessages=true, so queued system messages are left out of the response. Add &ignoreMessages=0 to the URL if you want Joomla's messages included in the messages field.
6.3 How Errors Are Returned
If your code throws an exception, com_ajax catches it. In JSON format, success becomes false. In raw format, it logs the error, sets the HTTP status code from the exception, and prints the class and message. This is why format=debug is useful during development: errors are easier to see.
This is why you should throw an exception to signal a problem, and never call die() or exit() in a handler:
// Good: com_ajax turns this into a clean error response.
throw new \RuntimeException('Inventory item not found', 404);
// Bad: kills the request and breaks the JSON the browser expects.
die('error');
A thrown exception gives the browser a structured response with the right status code. A raw die() produces broken output that JavaScript cannot parse.
7. Security and the Back End
An open endpoint that returns data is a security surface. com_ajax has several built-in protections, and Joomla 6 adds an important new one for the back end.
7.1 Site Versus Administrator Endpoints
There are two entry points:
index.php?option=com_ajax&...- the public front end.administrator/index.php?option=com_ajax&...- the back end. The admin file just includes the site file, so the logic is shared.
On the front end, anyone can call an endpoint, so your plugin must check permissions itself. On the back end, com_ajax normally requires a logged-in administrator.
7.2 The AllowUnauthorizedAdministratorAccess Attribute
Sometimes a back-end Ajax call must run before the admin has logged in - for example, a passwordless login challenge or a captcha check on the login screen. Joomla 6 (introduced in 5.4.4) handles this with a PHP attribute.
If a guest calls com_ajax in the back end, com_ajax inspects the target method. Unless that method is marked with the attribute, it refuses the call with a "not authorised" error.
use Joomla\CMS\Plugin\Attribute\AllowUnauthorizedAdministratorAccess;
#[AllowUnauthorizedAdministratorAccess]
public function onAjaxMyLoginStep(AjaxEvent $event): void
{
// This may run for a guest in the administrator area,
// because the attribute explicitly allows it.
}
This is a deliberate "deny by default" design: a back-end endpoint is closed to guests unless you opt in, one method at a time.
7.3 Caching and Indexing
com_ajax protects itself in two more ways on every request:
- It calls
allowCache(false), so responses are never served from the page cache. Each call runs fresh. - It sets the header
X-Robots-Tag: noindex, nofollow, so search engines do not index the endpoint URLs.
8. Under the Hood
For developers, it helps to know how little there is inside com_ajax. Once you have seen the flow, nothing about it is mysterious.
8.1 There Is No MVC
Most components have controllers, models, and views. com_ajax has none. The entire component is a single file, components/com_ajax/ajax.php, plus a one-line admin wrapper. There are no database tables that belong to com_ajax.
8.2 The Dispatch Flow
Here is what happens, top to bottom, for one request:
Request: option=com_ajax
│
v
allowCache(false) + X-Robots-Tag: noindex
│
v
Is 'format' set? ── no ──→ 404 "specify a format"
│ yes
v
plugin? ──→ import group, dispatch onAjax<Name>, read 'result'
module? ──→ check enabled, call Mod<Foo>Helper::<method>Ajax()
template? ──→ check enabled, call Tpl<Foo>Helper::<method>Ajax()
│
v
Format the result (json / raw) and echo it
8.3 The Extension Lookups
For modules and templates, com_ajax queries the #__extensions table to confirm the target is installed and enabled before it calls anything. This is why a disabled module returns "not accessible" instead of running.
| Table | Why com_ajax reads it |
|---|---|
#__extensions |
Confirm the module or template is installed and enabled = 1. |
Plugins are handled through PluginHelper::importPlugin(), which only loads enabled plugins, so a disabled Ajax plugin never fires.
9. Performance and Caching
com_ajax is convenient, but it is not free. Knowing what each call really costs helps you keep an interactive page fast.
9.1 Every Call Is a Full Joomla Boot
An Ajax request is not a lightweight microservice call. Each one boots the entire Joomla stack before your method runs:
Request
│
v
Framework + autoloader
│
v
Application + DI container
│
v
Session + user
│
v
Language files
│
v
Plugins imported
│
v
Your Ajax handler
│
v
JsonResponse
The work to answer "what is the stock of item 42?" includes all of that setup. So treat Ajax calls as real requests: make fewer of them, and return more per call instead of firing many tiny ones.
9.2 Keep the Handler Thin
An Ajax method should do three things: read and check the input, call your logic, and return the result. Keep the real work in a separate service or helper class, not in the handler itself.
public function onAjaxInventory(AjaxEvent $event): void
{
$app = $event->getApplication();
$id = $app->getInput()->getInt('id');
// The handler stays thin; the service does the work.
$event->addResult($this->inventory->getStock($id));
}
A thin handler is easier to test, easier to read, and easier to reuse from a normal controller later.
9.3 Cache Expensive Results
com_ajax disables the page cache for its own responses, but nothing stops you from caching the data your handler produces. Values that change rarely are ideal:
- Category trees and menus.
- Country, region, and currency lists.
- Product catalogues and search suggestions.
Use Joomla's cache for these lookups so a popular endpoint does not hit the database on every keystroke. On the browser side, debounce rapid events such as typing, so you send one request after a short pause instead of one per character.
Back to top10. com_ajax Versus the Web Services API
Joomla has a second way to return data without a page: the Web Services API at /api/index.php. They overlap, so it helps to know when to pick which.
| Question | com_ajax | Web Services API |
|---|---|---|
| Who calls it? | Mostly your own site's JavaScript | External apps, mobile, integrations |
| How much to build? | One method on a plugin or module | A full controller, view, and routes |
| Authentication | Joomla session and CSRF token | API token in a header |
| Best for | Small, page-specific interactions | Structured, reusable REST endpoints |
A REST call to the Web Services API looks like this, with the token in a header:
curl -H "X-Joomla-Token: <token>" \
https://example.test/api/index.php/v1/content/articles
Rule of thumb: use com_ajax for quick, in-page interactions on your own site, and the Web Services API when an outside system needs a stable, documented endpoint.
Back to top11. SEO and Metadata
com_ajax is not a content URL, and you should never let it behave like one.
- com_ajax already sends
X-Robots-Tag: noindex, nofollow, so its URLs stay out of search results. Do not undo this. - Do not create menu items that point at com_ajax. It has no human-readable view, so it would produce a broken page.
- Keep Ajax responses small and data-only. They are for scripts, not for crawlers.
- If an Ajax response builds part of a page, make sure the page also works without JavaScript, so search engines still see the core content.
In short: com_ajax serves data to the browser, while your normal pages serve content to people and search engines. Keep the two jobs separate.
Back to top12. Common Mistakes and Pitfalls
12.1 Missing Format
Symptom: every call returns a 404 and the message "Please specify a valid response format".
Fix: always add &format=json or &format=raw to the URL. com_ajax never assumes a format.
12.2 Wrong Event Name
Symptom: your plugin is enabled but nothing happens, and the result is empty.
Fix: the event is onAjax plus the capitalised plugin value. For plugin=hello the method must subscribe to onAjaxHello. Check the spelling and the capital letter.
12.3 The Module or Template Is Disabled
Symptom: "Module mod_foo is not published, you do not have access to it, or it is not assigned to the current menu item."
Fix: enable the module or template. com_ajax checks #__extensions and refuses disabled targets.
12.4 Forgetting the CSRF Token on Writes
Symptom: a save or delete works in testing but is insecure, or fails once token checks are added.
Fix: send the token (X-CSRF-Token header or a token field) for any request that changes data, and verify it with Session::checkToken() on the server.
12.5 Calling a Back-End Endpoint as a Guest
Symptom: a back-end Ajax call fails with a "not authorised" error for users who are not logged in.
Fix: that is correct behaviour in Joomla 6. If the method genuinely must run for a guest in the admin area, mark it with the AllowUnauthorizedAdministratorAccess attribute - and only then.
12.6 Trusting Raw Input
Symptom: unexpected data, or a security report, because input was used without filtering.
Fix: read parameters with the typed getters (getInt, getString), and check user permissions inside your method, not just in the URL.
13. Best Practices
If you only remember a few things from this article, remember these:
- Always send a
format. com_ajax will not guess one for you. - Prefer an Ajax plugin for reusable logic; use a module or template helper only when the logic clearly belongs there.
- Use
format=jsonfor data and read it from thedatafield; useformat=rawfor HTML fragments. - Send and verify a CSRF token for anything that changes data; keep GET requests read-only.
- Filter all input with the typed getters, and authorise every action with Joomla's ACL.
- Keep the handler thin: read input, call a service, return the result. Put real logic in its own class.
- Remember each call is a full Joomla boot. Make fewer, larger calls, and cache results that change rarely.
- Throw exceptions to report errors; never use
die()orexit()in a handler. - In the back end, keep endpoints closed to guests; open a method to guests only with the attribute, when truly needed.
- Use com_ajax for in-page interactions, and the Web Services API for external integrations.
14. Quick Reference
WHAT IT IS A core component that routes a small request to one extension method
ENTRY POINT index.php?option=com_ajax (and administrator/index.php for the back end)
ON DISK components/com_ajax/ajax.php (no MVC, no tables of its own)
FORMAT format=json | raw | debug (REQUIRED)
PLUGIN &plugin=hello&group=ajax ──→ event onAjaxHello (AjaxEvent)
MODULE &module=foo&method=get ──→ ModFooHelper::getAjax()
TEMPLATE &template=bar&method=get ──→ TplBarHelper::getAjax()
SET RESULT $event->addResult($data) or $event->updateEventResult($data)
READ INPUT $event->getApplication()->getInput()->getInt('id')
JSON SHAPE { success, message, messages, data }
MESSAGES add &ignoreMessages=0 to include enqueued system messages
WRITE SAFELY send X-CSRF-Token; verify with Session::checkToken()
AUTHORISE $user->authorise('core.edit', 'com_xxx') inside the handler
ADMIN GUEST mark method with #[AllowUnauthorizedAdministratorAccess]
PROTECTIONS allowCache(false) + X-Robots-Tag: noindex, nofollow
COST every call is a full Joomla boot - cache, debounce, batch
Back to top15. Summary
com_ajax is one of the smallest components in Joomla, yet it unlocks a large amount of interactivity. Instead of building a full component just to return one value, you expose a single method and let Joomla handle the request, the routing, and the response.
The key ideas are short:
- One endpoint,
option=com_ajax, routes to a plugin, a module, or a template. - Plugins answer the event
onAjax<Name>through a typedAjaxEvent; modules and templates answer with a<method>Ajax()helper. - A
formatis always required, and JSON responses follow a fixed envelope. - Security comes first: filter input, use the CSRF token for writes, and keep back-end endpoints closed to guests unless you opt in.
If you are adding live search, a save-without-reload button, or a small data feed to your site, com_ajax is usually the cleanest way to do it. And if you need help designing a secure Ajax endpoint, tracking down why one returns nothing, or deciding between com_ajax and the Web Services API, that is exactly the kind of advanced Joomla work I do every day.
Back to top

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.
Frequently Asked Questions
AJAX enables dynamic content loading without page reloads, letting users interact instantly. It keeps the page feeling fluid, great for modern, responsive sites.
Joomla’s built-in AJAX interface helps. You’ll write a server-side method in your component, then a JavaScript call to send data and update part of the page seamlessly.
Yes, but you must sanitize inputs on the server side, ensure proper access control, and validate any data received to prevent security risks.
Absolutely. AJAX avoids full page reloads, making interactions faster. It reduces bandwidth use and keeps users engaged.
Yes, a basic understanding is essential. You’ll write JavaScript to send AJAX requests and update content dynamically on the page.
Use browser developer tools like the network tab to watch AJAX calls. You can see the data sent and received, helping you troubleshoot issues.


