Skip to main content

Web Services API in Joomla

16 June 2026

Most people use Joomla through a browser: they log in, click around the backend, and edit content in a form. But Joomla can also be driven by another program, with no browser and no human at all. A mobile app, a second website, or a nightly script can read and write your content directly. The doorway for all of this is the Web Services API, Joomla's built-in REST interface.

This article explains how Joomla's REST Web Services API really works. It covers the basics for owners and editors, the setup for administrators, and the technical details for developers. You will learn what the API is, how to switch it on, how a request proves who it is, how to read and write data with simple curl calls, and how the routing works under the hood so you can add endpoints of your own.

The same Joomla, without the browser: every article, user, and menu becomes a URL a program can call.

The goal is simple: help you understand the Web Services API well enough to enable it safely, call it with confidence, and extend it when you need to.

1. The Basics

1.1 What is the Web Services API?

The Web Services API is a second front door to your Joomla site. Instead of returning HTML pages for a browser, it returns plain data that a program can read. A request asks for something (for example "give me all published articles"), and Joomla answers with structured JSON rather than a styled web page.

It is a REST API. REST means you work with resources (articles, users, menu items) through normal HTTP methods: GET to read, POST to create, PATCH to update, and DELETE to remove. The same idea you already know from the backend (list, view, save, delete) maps onto these four verbs.

1.2 Where It Lives

The API has its own application, separate from the public website and the administrator backend. It is reached through the /api/ folder, and every endpoint starts with a version prefix:

https://example.test/api/index.php/v1/content/articles

Joomla ships three applications that share one codebase and one database:

ApplicationFolderServes
Site / The public website (HTML).
Administrator /administrator/ The backend (HTML).
API /api/ The Web Services API (JSON).

1.3 Why You Would Use It

The API is useful whenever something other than a person needs your content:

  • A mobile app shows your articles without scraping HTML.
  • Another site pulls in your latest news automatically.
  • A script imports hundreds of articles or users in one run.
  • An external system (a CRM, a shop, a dashboard) keeps data in sync with Joomla.

Anything you can do by hand in the backend, the API lets a program do for you, repeatedly and reliably.

Back to top

2. JSON:API - the Format Joomla Speaks

2.1 A Shared Convention

Joomla does not invent its own response shape. It follows JSON:API, a public convention for how a REST response should look. Because the format is standard, many existing client libraries already understand it, and the structure is the same for articles, users, or any other resource.

2.2 The Shape of a Single Item

A request for one article returns a data object with a type, an id, and an attributes block that holds the actual fields:

{
  "links": { "self": "https://example.test/api/index.php/v1/content/articles/1" },
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "Welcome to my site",
      "alias": "welcome-to-my-site",
      "state": 1,
      "catid": 2,
      "introtext": "..."
    }
  }
}

2.3 The Shape of a List

A list request returns data as an array of those objects, plus a meta block with the total count and links for paging through the results. Knowing this shape once means you can read every list the API returns, whatever the resource.

Back to top

3. Turning the API On

3.1 The Global Switch

The API is controlled in System → Global Configuration. The relevant settings group is small but important. Until the API is enabled, every call returns an error, which is the safe default for a brand new site.

3.2 The Web Services Plugins

Each component exposes its endpoints through a Web Services plugin. These plugins live in the webservices plugin group, and each one registers the routes for its component. If the plugin for a component is disabled, that component has no API, even when everything else is on.

Plugin (group webservices)Opens up
Web Services - Content Articles, categories, fields, history.
Web Services - Users Users, groups, access levels.
Web Services - Menus Site and administrator menus and items.
Web Services - Banners, Contact, Newsfeeds, Tags, Media, ... The matching component's data.

To find them, open System → Manage → Plugins and filter the list by the webservices type. Enable only the ones you actually need.

3.3 The Setup Checklist

  1. In Global Configuration, enable the Web Services API.
  2. Enable the webservices plugin for each component you want to reach.
  3. Set up authentication (see the next section), so calls can prove who they are.
  4. Make a first read-only GET call to confirm the API answers.
Back to top

4. Authentication - Proving Who You Are

4.1 No Form, No Session

The website remembers you with a session cookie after you log in once. The API has no login form and keeps no session. A program must prove who it is on every single request. Joomla supports two methods, both handled by the api-authentication plugin group.

Each user gets a personal token, a long secret string, and sends it as a header on every call. No password ever leaves the server, and a token can be revoked on its own without changing the user's password.

curl -H "X-Joomla-Token: <your-token>" \
     https://example.test/api/index.php/v1/content/articles

# Joomla also accepts the standard Authorization header (Bearer is case-sensitive):
curl -H "Authorization: Bearer <your-token>" \
     https://example.test/api/index.php/v1/content/articles

To make tokens work, enable two plugins and generate a token:

  1. Enable the User - Joomla API Token plugin (it adds the token field to user profiles).
  2. Enable the API Authentication - Token plugin (it reads the header on each request).
  3. Open the user's profile, find the Joomla API Token tab, and generate a token. Copy it once: Joomla stores only a seed, so it cannot show the token to you again.

By default only Super Users may use a token. Widen this through the allowedUserGroups parameter of the User - Joomla API Token plugin, and keep that list as narrow as the job allows.

4.3 Basic Authentication

The API Authentication - Web Services Basic plugin lets a client send a username and password with each request, using standard HTTP Basic auth:

curl -u myusername:mypassword \
     https://example.test/api/index.php/v1/content/articles

It is simple, but it sends the real account password on every call. Use it only over HTTPS, and prefer tokens for anything long-lived, because a leaked password is far more damaging than a leaked token you can revoke.

4.4 Public Routes

A few routes can be marked public, which means they answer without any credentials. Most write routes are never public. Read routes are public only when the component's Web Services plugin opts in. Treat public access as the exception, not the rule, and never expose write methods that way.

Back to top

5. Your First Requests (CRUD)

5.1 The Four Verbs

Every resource follows the same pattern. Using articles as the example:

GoalMethodEndpoint
List all articles GET /v1/content/articles
Read one article GET /v1/content/articles/1
Create an article POST /v1/content/articles
Update an article PATCH /v1/content/articles/1
Delete an article DELETE /v1/content/articles/1

5.2 Reading Data

A read is the safest call to start with, because it changes nothing:

curl -H "X-Joomla-Token: <your-token>" \
     https://example.test/api/index.php/v1/content/articles/1

5.3 Creating Data

To create an article, send a POST with a JSON body. Tell Joomla the body is JSON with a Content-Type header:

curl -X POST \
     -H "X-Joomla-Token: <your-token>" \
     -H "Content-Type: application/json" \
     -d '{"title":"My new article","catid":2,"articletext":"Hello world","language":"*"}' \
     https://example.test/api/index.php/v1/content/articles

5.4 Updating and Deleting

A PATCH changes only the fields you send, leaving the rest untouched. A DELETE removes the item (for articles, it follows the normal trash and delete rules of the component):

curl -X PATCH \
     -H "X-Joomla-Token: <your-token>" \
     -H "Content-Type: application/json" \
     -d '{"title":"An updated title"}' \
     https://example.test/api/index.php/v1/content/articles/1

curl -X DELETE \
     -H "X-Joomla-Token: <your-token>" \
     https://example.test/api/index.php/v1/content/articles/1

The user behind the token keeps all their normal permissions. If they cannot edit an article in the backend, the API will refuse the same edit. The API is never a way around the access rules.

Back to top

6. Querying Lists

6.1 Paging Through Results

A list does not return every row at once. Joomla pages the results using the JSON:API offset notation. You control the window with two query parameters:

ParameterMeaning
page[limit] How many items to return.
page[offset] How many items to skip before the window starts.
curl -H "X-Joomla-Token: <your-token>" \
  "https://example.test/api/index.php/v1/content/articles?page[limit]=20&page[offset]=0"

The meta block in the response tells you the total count, so you know how many pages there are.

6.2 Sorting

Order a list with list[ordering] for the column and list[direction] for the way (asc or desc). Joomla checks the column against the model's allowed filters, so you cannot sort by an arbitrary field:

"...?list[ordering]=created&list[direction]=desc"

6.3 Filtering

Narrow a list with filter[...] parameters. The available filters depend on the component, and they match the same filters the backend list uses. For articles you can, for example, filter by category or by published state:

"...?filter[catid]=2&filter[state]=1"

6.4 Choosing Fields

To keep a response small, ask for only the fields you need with the JSON:API fields parameter, naming the resource type:

"...?fields[articles]=title,catid,state"

Remember to URL-encode the square brackets and to quote the whole URL in your shell, or the brackets and ampersands will be misread.

Back to top

7. What Endpoints Exist

7.1 The Core Resources

Each enabled Web Services plugin adds a family of routes. These are the most useful core endpoints, all under /api/index.php/:

ResourceBase route
Articles v1/content/articles
Content categories v1/content/categories
Users v1/users
User groups / access levels v1/users/groups, v1/users/levels
Site / admin menus v1/menus/site, v1/menus/administrator
Menu items v1/menus/site/items
Contacts v1/contacts
Newsfeeds v1/newsfeeds/feeds
Banners v1/banners
Tags v1/tags
Media files v1/media/files
Custom fields v1/fields/content/articles
Global / component config v1/config/application, v1/config/:component
Installed extensions v1/extensions

7.2 Sub-Resources

Some routes nest under an item to expose related data. For example, an article's version history sits under the article it belongs to:

v1/content/articles/1/contenthistory

The exact set of routes always depends on which plugins you enabled. When a route returns "404 Not Found" although the URL looks right, the matching Web Services plugin is usually disabled.

Back to top

8. Under the Hood (Developer View)

8.1 The Request Journey

An API request travels a clear path from URL to JSON:

/api/index.php   →  API application boots
                 →  ApiRouter matches the URL to a route
                 →  an api-authentication plugin checks the token
                 →  the component's ApiController runs (list/item/add/...)
                 →  a ListModel/AdminModel fetches or saves the data
                 →  a JsonapiView + serializer build the JSON:API response

8.2 Routes Come From Plugins

The API has no fixed route table. Each Web Services plugin listens for the onBeforeApiRoute event and registers its own routes when the router is built. The com_content plugin, for example, does this:

public function onBeforeApiRoute(BeforeApiRouteEvent $event): void
{
    $router = $event->getRouter();

    $router->createCRUDRoutes(
        'v1/content/articles',
        'articles',
        ['component' => 'com_content']
    );
}

The helper createCRUDRoutes() is the reason most resources share the same five-verb shape. One call expands into all five REST routes:

GET    v1/content/articles        → articles.displayList
GET    v1/content/articles/:id    → articles.displayItem
POST   v1/content/articles        → articles.add
PATCH  v1/content/articles/:id    → articles.edit
DELETE v1/content/articles/:id    → articles.delete

Its fourth argument, $publicGets, decides whether the two GET routes answer without authentication. It defaults to false.

8.3 The Controller and View

Each component has a slim API layer under api/components/com_xxx/. A controller extends ApiController and reuses the same models as the backend. It reads the paging, ordering, and filter parameters from the request, runs the model, and hands the result to a JsonapiView. A serializer then turns the Joomla object into the JSON:API type / id / attributes shape.

api/components/com_content/src/
├─ Controller/ArticlesController.php   (extends ApiController)
├─ View/Articles/JsonapiView.php       (renders JSON:API)
└─ Serializer/ContentSerializer.php    (maps fields to attributes)

Because the API reuses the existing models, the same validation, access checks, and events that run in the backend also run for an API call. The API is a new face on the same engine, not a parallel one.

8.4 Tables Involved

TableRole
#__extensions Where the webservices and api-authentication plugins are enabled.
#__user_profiles Holds the API token seed (joomlatoken.token) and its enabled flag.
#__users The account behind a token or Basic login, with all its groups.

8.5 Adding Your Own Endpoint

To expose a custom component over the API, you write a small webservices plugin that registers routes on onBeforeApiRoute, and an API controller, view, and serializer under your component's api/ folder. You never patch the core or touch the existing routes. This is the same mechanism every core component uses, which means a third-party extension can join the API as a first-class citizen.

Back to top

9. Access Control (ACL) and Permissions

9.1 Authentication Is Not Authorisation

Two different checks guard every write to the API, and it helps to keep them apart:

  • Authentication answers "who are you?" This is the token or Basic login from section 4.
  • Authorisation answers "are you allowed to do this?" This is Joomla's ACL (Access Control List).

A valid token only gets you in the door. What you may then read, create, edit, or delete is decided by the same permission system that governs the backend. The API does not have its own rules, and it offers no way around the existing ones.

9.2 The Permission Actions

The API controller checks the user against the standard Joomla permission actions before it runs a write. They are exactly the actions you set on the Permissions tab of a component or an item in the backend:

ActionNeeded for
core.create A POST that creates a new item.
core.edit A PATCH that changes an item.
core.edit.own Editing only items the user created.
core.edit.state Changing the published state (publish, unpublish, trash).
core.delete A DELETE that removes an item.

Under the hood the controller asks the logged-in identity directly, for example $user->authorise('core.delete', $this->option). If the answer is no, the call is refused before any data changes.

9.3 How a Permission Is Resolved

A permission is never stored as a simple yes or no on the user. Joomla works it out by walking from the user up through their groups and down through the asset tree, where each component and item is an asset with its own rules in the #__assets table:

User
  → the groups the user belongs to
  → rules on the asset (component or item)  [#__assets]
  → inherited rules from parent assets
  → final Allow or Deny

An explicit Deny anywhere up the chain always wins, which is why a permission can be switched off globally and never re-enabled by a lower level. This is the same calculation the backend Permissions tabs perform, so the API behaves exactly like an administrator action with the same account.

9.4 What This Means in Practice

Two HTTP responses tell you which check failed:

StatusMeaning
401 Unauthorized Authentication failed. The token or login was missing or wrong.
403 Forbidden Authentication worked, but the user lacks the ACL permission for this action.

The practical takeaway is to give each integration a dedicated user in a group that holds only the permissions the job needs. A read-only sync should sit in a group that can view but not edit; a content importer needs core.create but rarely core.delete. Never reach for a Super User account just to make a call succeed, because that hands the token far more power than the task requires.

Back to top

10. Going Further: Headless, AI, and Scale

Once the API is the way your content leaves Joomla, a bigger question follows: what consumes it? This is where Joomla stops being only a website and becomes a content platform. The same JSON you have been calling can feed a decoupled frontend, an AI assistant, or a fleet of channels at once.

  • Headless Joomla. Drop Joomla's own rendering and let a separate frontend (React, Vue, Next.js, a mobile app) draw the pages from the API. Joomla becomes a pure content store.
  • AI and RAG. Because every article is clean JSON, Joomla makes a natural source of truth for chatbots and Retrieval-Augmented Generation pipelines that answer from your own content.
  • Performance and scale. A busy API setup leans on paging, caching (Redis, Varnish, CDN), rate limiting, and token rotation to stay fast and safe.

These topics are an architecture subject in their own right, so they have their own companion piece. For the full picture - decoupled frontends, a Next.js example, AI chatbots, RAG and vector databases, MCP, AI agents, caching, and SEO for a decoupled site - see the separate Focus On article on using Joomla as a headless website. This article stays focused on the API itself: how it works, how to call it, and how to secure it.

Back to top

11. SEO and Metadata

The Web Services API has no direct SEO value, and it should not. It returns JSON for machines, not pages for search engines to index, so there is nothing here for a crawler to rank.

The indirect points matter more. Keep the /api/ endpoints out of your sitemap and do not link to them from public pages. Serve every call over HTTPS, because tokens and Basic credentials must never travel in the clear. And never place a token in a URL query string, a public repository, or a browser script, where a crawler or a curious visitor could capture it. A leaked credential harms you far more than any ranking ever could.

Back to top

12. Common Mistakes and Pitfalls

12.1 The API Is Off

Symptom: every call returns an error, even a simple read.

Fix: enable the Web Services API in System → Global Configuration. It is off by default on a new site.

12.2 The Component Plugin Is Disabled

Symptom: one resource returns 404 while another works fine.

Fix: enable the matching webservices plugin. Without it, that component registers no routes.

12.3 The Token Is Rejected (401)

Symptom: a token that looks correct is refused as unauthorised.

Fix: check three things. The API Authentication - Token plugin must be enabled, the user must belong to a group listed in allowedUserGroups, and the account must not be blocked or pending activation. Remember that the Bearer keyword is case-sensitive.

12.4 The Shell Eats the URL

Symptom: paging or filtering is ignored, or the shell throws an error.

Fix: wrap the whole URL in quotes and escape the ampersand. Square brackets and & have special meaning in most shells, so "...?page[limit]=20&page[offset]=0" needs the quotes.

12.5 Forgetting the Content-Type

Symptom: a POST or PATCH creates nothing, or the body is ignored.

Fix: send Content-Type: application/json with every write, and make sure the body is valid JSON.

12.6 Expecting the API to Bypass Permissions

Symptom: a call is refused even though the token is valid.

Fix: the user behind the token needs the same permission they would need in the backend. Grant the right group access; do not look for an API-only override, because there is none.

Back to top

13. Best Practices

If you remember only a few things from this article, remember these:

  • Enable only the webservices plugins you actually use. Every enabled one widens the surface.
  • Prefer token authentication over Basic, and keep allowedUserGroups as narrow as possible.
  • Treat a token like a password: HTTPS only, never in a URL, a repository, or a browser script, and revoke it the moment it might leak.
  • Create a dedicated API user with just the permissions the integration needs, instead of using a Super User.
  • Start every integration with a read-only GET to confirm access before you write anything.
  • Use page[limit] to page through large lists; do not try to pull thousands of rows in one call.
  • Add custom endpoints with your own webservices plugin. Never patch the core routes.
Back to top

14. Quick Reference

BASE URL     https://example.test/api/index.php/v1/...

VERBS        GET     read (list or single item)
             POST    create
             PATCH   update (only the fields you send)
             DELETE  remove

AUTH         X-Joomla-Token: <token>        (recommended)
             Authorization: Bearer <token>   (Bearer is case-sensitive)
             curl -u user:pass               (Basic, HTTPS only)

ACL          core.create  core.edit  core.edit.state  core.delete
             401 = not authenticated   403 = not authorised

LIST QUERY   page[limit]=20  page[offset]=0
             list[ordering]=created  list[direction]=desc
             filter[catid]=2  filter[state]=1
             fields[articles]=title,catid,state

ENDPOINTS    v1/content/articles      v1/content/categories
             v1/users                 v1/users/groups
             v1/menus/site/items      v1/contacts
             v1/banners               v1/tags
             v1/media/files           v1/extensions

ENABLE       Global Config        turn the API on
             webservices plugin   per component (routes)
             api-auth + user      Token plugins (login)

SCALE        page large lists, request only needed fields
             caching, CDN, rate limiting: see the headless article

DIAGNOSE     SELECT name, folder, enabled FROM #__extensions
             WHERE folder IN ('webservices','api-authentication');
Back to top

15. Summary

The Web Services API turns your whole Joomla site into something a program can drive:

  • It is a REST/JSON:API interface under /api/index.php/v1/..., separate from the website and backend but sharing the same database and models.
  • You switch it on in Global Configuration, then enable a webservices plugin for each component you want to expose.
  • Every request authenticates, with a token (recommended) or Basic auth, and is then checked against Joomla's ACL, so it keeps the user's normal permissions.
  • The four verbs GET, POST, PATCH, and DELETE map onto list, read, create, update, and delete.
  • Lists support paging, ordering, filtering, and field selection through query parameters.
  • Under the hood, plugins register routes on onBeforeApiRoute, createCRUDRoutes() builds the five REST routes, and a serializer shapes the JSON:API output.
  • It is the foundation for headless frontends, AI and RAG pipelines, and integrations, which the companion article on headless Joomla covers in depth.

Once you see the API as the same Joomla without the browser, it stops being mysterious. You know where to enable it, how a call proves who it is, and how the routing turns a URL into data.

If you are planning an integration, a mobile app, or a data migration that leans on the Web Services API, the safe path is to enable only what you need, lock down the token and its user group, and test each endpoint before you trust it in production. Designing that setup so it is both convenient and secure is exactly the kind of careful work a Joomla specialist does before the first line of integration code is written.

Back to top
Web Services API in Joomla
Peter Martin
Peter Martin

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