Skip to main content

AJAX in Joomla

29 June 2026

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
ParameterMeaning
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.

Back to top

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 parameterWhat gets calledBest 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=json wraps the result in a standard JSON envelope.
  • format=raw returns the result as plain text or HTML, with no wrapper.
  • format=debug is handy while developing, because it shows errors more openly.

Section 6 covers the formats in detail.

Back to top

3. 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:

  1. It imports every plugin in the requested group (default ajax; override it with &group=system and similar).
  2. It fires the event onAjaxHello - the word after plugin=, capitalised, with onAjax in 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:

MethodWhat 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.

Back to top

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 #__extensions table 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 top

5. 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 top

6. 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!" }
}
FieldMeaning
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.

Back to top

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.
Back to top

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.

TableWhy 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.

Back to top

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 top

10. 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.

Questioncom_ajaxWeb 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 top

11. 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 top

12. 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.

Back to top

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=json for data and read it from the data field; use format=raw for 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() or exit() 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.
Back to top

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 top

15. 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 typed AjaxEvent; modules and templates answer with a <method>Ajax() helper.
  • A format is 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
AJAX in Joomla
Peter Martin
Peter Martin
Joomla Specialist

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.

Frequently Asked Questions

What is AJAX in Joomla and why should I use it?

AJAX enables dynamic content loading without page reloads, letting users interact instantly. It keeps the page feeling fluid, great for modern, responsive sites.

How do I implement AJAX in a Joomla component?

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.

Is AJAX safe to use on Joomla websites?

Yes, but you must sanitize inputs on the server side, ensure proper access control, and validate any data received to prevent security risks.

Can AJAX improve my Joomla site’s performance?

Absolutely. AJAX avoids full page reloads, making interactions faster. It reduces bandwidth use and keeps users engaged.

Do I need to know JavaScript to use AJAX in Joomla?

Yes, a basic understanding is essential. You’ll write JavaScript to send AJAX requests and update content dynamically on the page.

How can I debug AJAX calls in Joomla?

Use browser developer tools like the network tab to watch AJAX calls. You can see the data sent and received, helping you troubleshoot issues.