
Web Services API in Joomla
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:
| Application | Folder | Serves |
|---|---|---|
| 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 top2. 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.
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
- In Global Configuration, enable the Web Services API.
- Enable the
webservicesplugin for each component you want to reach. - Set up authentication (see the next section), so calls can prove who they are.
- Make a first read-only
GETcall to confirm the API answers.
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.
4.2 Token Authentication (Recommended)
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:
- Enable the User - Joomla API Token plugin (it adds the token field to user profiles).
- Enable the API Authentication - Token plugin (it reads the header on each request).
- 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 top5. Your First Requests (CRUD)
5.1 The Four Verbs
Every resource follows the same pattern. Using articles as the example:
| Goal | Method | Endpoint |
|---|---|---|
| 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 top6. 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:
| Parameter | Meaning |
|---|---|
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 top7. 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/:
| Resource | Base 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 top8. 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
| Table | Role |
|---|---|
#__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.
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:
| Action | Needed 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:
| Status | Meaning |
|---|---|
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.
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 top11. 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.
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 top13. Best Practices
If you remember only a few things from this article, remember these:
- Enable only the
webservicesplugins you actually use. Every enabled one widens the surface. - Prefer token authentication over Basic, and keep
allowedUserGroupsas 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
GETto 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
webservicesplugin. Never patch the core routes.
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 top15. 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
webservicesplugin 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

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


