Joomla! World Conference 2026

15 minutes reading time (2952 words)

Using Bruno (the API Client) to play a.o. with Joomla Web Services

Bruno API

1 Introduction

In the past, many people were typically using Postman as API Client when testing Web Services / APIs.

More recently, many users have moved from Postman to Bruno. Here are the main reasons:

  • Open-Source & Free: Bruno is an open-source alternative, providing a fast, lightweight tool that remains free for core functionalities, unlike Postman’s restrictive, tiered, and paid, feature-packed model.
  • No Login/Cloud Required: Bruno allows immediate, unrestricted use without the need to create an account, log in, or worry about cloud-based synchronization.
  • Local-First & Privacy-Focused: Unlike Postman, which stores data in the cloud, Bruno saves your API Collections, requests, and variables directly in a folder on your filesystem (using the .bru format). This ensures no data leaves your machine and works perfectly offline.
  • Git-Friendly (Built-in Version Control): Because Bruno stores Collections as text files, you can use Git to version control, branch, and merge your API requests alongside your code, facilitating easy collaboration without proprietary tools.
  • Lightweight Performance: As a desktop application, Bruno is faster and consumes less memory compared to the increasingly heavy, browser-based, or Electron-based, Postman interface.
  • Simple & Focused: It specializes in API testing and development without the bloat of advanced, often unnecessary, features found in Postman.

The aim of this presentation is to get you started with Bruno.

It is a.o. inspired by a Session made by René Kreijveld at JoomlaCamp 26 on February 28th 2026 in Essen (Germany).

2 Resources

A big up respectively to Alexandre, Herman & René!

3 Installation

3.1 Install Bruno

Download and install Bruno from the official website: https://www.usebruno.com

Note: if you already have Bruno installed, make sure you update it to the latest version

3.2 Install the Collection

3.2.1 Download the Collection

  • Go to https://github.com/renekreijveld/bruno-joomla-api
  • Click on the green <> Code button
  • Click on Download ZIP
  • Save it on your computer
  • Unzip it
  • Put the bruno folder and its content whereever you wish
    • for example at the root of your local website
    • or simply in a more general folder

download

3.2.2 Open the Collection in Bruno

  • open Bruno
  • click on the + sign next to Collections in the left sidebar
  • select Open Collection

open-Collection
  • navigate to the location where you copied the bruno folder
  • open it
  • click on the Joomla Web Services API folder
  • and click the Open button

bruno-folder
  • in the left sidebar below Collections you now see the Joomla Web Services API Collection
  • click on it to expand and see all the folders and requests in the Collection

Collection

3.2.3 Configure your Collection in Bruno

  • with the Collection open, at the top right you see a dropdown called No environment
  • click on it to expand the dropdown and select Joomla Web Services API
  • click on the button again to reopen the dropdown and click on Configure

configure
  • it opens an Environments tab
  • there are two ready-to-use variables
    • base_url
      • adapt the URL with the URL of your Joomla website
      • example: https://www.myjoomlawebsite.com/api/index.php
      • note: be sure to keep the /api/index.php part at the end of the URL, as that is the endpoint for Joomla API requests
    • api_key
      • copy the API token from your Joomla User Profile
        • ideally, you should create a new Super User instead of using your own Joomla User Profile so that
          • you can easily disable it (security)
          • you also see more clearly in the User Actions Logs what has been done via the backend vs via the Web Services
        • the Web Services can also be made available to Users not having the Super User Access Level but that is another topic
      • paste it as the value of the api_key variable
  • click the Save button at the bottom of the Environments tab

key

url-key

⚠️ Security Note: Since Bruno stores these variables in plain text files (.bru), ensure you never commit your specific api_key to a public Git repository. Use .gitignore for your environment file or share keys only within trusted teams

4 Use your Collection in Bruno

Now it is time to start playing and to use the requests in the Collection to make API calls to your Joomla website.

For this tutorial, we will use René Kreijveld’s Bruno collection.

4.1 Terminology

Some basic terminology first when working with APIs. You are probably familiar with the acronym CRUD (Create / Read / Update / Delete). The corresponding requests are the following:

  • Create = post
  • Read = get
  • Update = patch
  • Delete = del

4.2 A simple GET Request

When you make API calls a good start is always to run a simple GET request, especially if you have little experience (a POST request is not complicated, but there are already more chances to run into errors because you would forget some required Parameters, because you put a wrong Category ID, …).

  • in the left sidebar click on Joomla Web Services API to expand the Collection
  • click on Content Articles to see all possible requests on Articles
  • click on the the first option, ie GET content/articles, which retrieves a List of Articles
  • send the request
    • either by clicking on the right arrow icon at the top right of your screen, next to the </> [Generate Code] and the floppy disk [Save] icons
    • or by using the keyboard shortcut, ie CTRL+ENTER

articles

If everything is fine, on the Response tab you should then see

  • a green status 200 OK
  • and below the Response should contain the list of Articles of your Joomla website in JSON format (note that by clicking on the JSON button you could display the same Response in other formats like HTML, XML etc)

200

4.3 Using Parameters in Requests

On the Params tab, you see that we have a number of ready-to-use parameters

  • filter for (according to the context for example) Author, Category, Language, …
  • list for Ordering and Direction

In the following example

  • I check filter[category] and put 12 as value (because I do have a Category having ID 12 of course)
  • I check filter[search] and put Star Wars

When I run the request I then get the following result

parameters

4.4 Using Variables in Requests

Let’s now choose an another action, for example Content Articles > GET content/articles/{article_id}

On the newly opened Tab, note the URL which is mentioned after the GET: {{base_url}}/v1/content/articles/{{article_id}} where the Variable

  • {{base_url}} is green because we have already set a Value above
  • {{article_id}} is red because we have not yet set any Value

Simply click on the red {{article_id}} and in the popup you can specify the Value of an existing Article (in my case: 212) and press Enter key

article_id1

Note the {{article_id}} which was red has now become green

article_id2

If you want to assign another value, simply

  • click on {{article_id}} again
  • or go on the Tab Vars where you see all Variables and where you can
    • either uncheck the current Value ({{article_id}} will become red again)
    • either change its value

article_id3

When I run the request I then get the following result

article_id4

4.5 Making your Variables “global”

If for some reason you want to make a Variable “global”

  • go to our Environment (where we already configured base_url and api_key)
    • click on the Joomla Web... button at the top right of Bruno
    • click on Configure button
  • add a new line with for example
    • article_id (without the double curly brackets) as Name
    • the desired Value

variable

4.6 A simple POST request

  • click on Content Articles to see all possible requests on Articles
  • click on the the 2nd option, ie POST content/articles

By default, the example Body is the following

{
  "alias": "",
  "articletext": "",
  "catid": 0,
  "language": "",
  "metadesc": "",
  "metakey": "",
  "title": ""
}

But note that the only necessary fields are the following:

{
  "articletext": "",
  "catid": 2,
  "language": "",
  "title": "test post 2"
}

In particular

  • must be present + must have a real Value
    • title
    • catid
  • must be present in the body but can be empty (ie with "" as Value)
    • articletext
    • language. Note: when you want to put a value
      • * stands for All
      • en-GB for English
      • fr-FR for French etc (just like in your Joomla database)

Should you totally remove "articletext": "", from the Body then you would indeed get the following error:

{
  "errors": [
    {
      "title": "Save failed with the following error: Field 'introtext' doesn't have a default value",
      "code": 400
    }
  ]
}

4.7 articletext vs introtext + fulltext

As you know, in Joomla

  • introtext stands for the text in the Editor before the Read more button
  • fulltext stands for the text in the Editor after the Read more button

Actually, articletext is “smart” and will separate your Value into introtext and fulltext when detecting <hr id="system-readmore"> in the Value.

So in the Body you can use the combination that suits you the most:

  • articletext alone
  • introtext alone
  • both introtext + fulltext
  • [ but not fulltext without introtext being present. It can be empty though]

4.8 Basic Custom Fields

Suppose you have a Custom Field of Type Text, having

  • My Own Custom Field as Title
  • my-own-custom-field as Name (Alias)

Then in the Body of the API Request you would simply add the following line in order to feed the Custom Field with the Value lorem ipsum:

"my-own-custom-field": "lorem ipsum",

4.9 More advanced Custom Fields or Tags

Example: suppose you want to POST or PATCH an Article having a Custom Field of Type Checkboxes. In the following example its Name is cinecheck. If the Values I want to check are respectively 12, V, F, L and H then the Body of the API Request should be as follows. In particular note that you should not surround the JSON of the Custom Field with quotes.

{
    "title":"Demo from Bruno",
    "cinecheck":{"0":"12","1":"V","2":"F","3":"L","4":"H"},
    "note":"don't surround the JSON of the Custom Field of Type Checkboxes with quotes"
}

4.10 Other Fields

Actually, if you need to add any Field (native Field or Custom Field) in a Post Request, the easiest way is to first do a Get Request in order to see

  • what the Label is
  • and how the Value is formatted (example: dates are formatted like yyyy-mm-dd hh:mm:ss)

The following example illustrates how to add Fields like

  • access
  • created_by
  • state
  • … and associations (when you have a multilingual website you can indeed associate Articles between the different languages)
{
    "title": "lorem ipsum",
    "articletext": "",
    "catid": 2,
    "language": "en-GB",
    "state": 1,
    "access": 3,
    "created_by": 100,
    "associations": {
        "nl-NL": "13",
        "fr-FR": "14"
    }
}

4.11 What if you POST an Article and the Alias is already taken in the given Category?

With J!4.x, J!5.x and J!6.0.x, you will get an error message if you POST an Article for which the Alias is already taken in the given Category. This can sometimes be annoying when you are automating Article creation.

But as of J!6.1 (release date: mid-April 2026) you won’t have to worry about that in your workflow because Joomla will automatically adapt the Alias. Example: if the Alias my-test-article-1 is already taken, it will give my-test-article-1.

Thanks to Nicola Galgano (aka @alikon) for improving this!

Want to know more about this? Check

4.12 A POST request to upload an image in a given folder

  • click on Media Files to see all possible requests on Media
  • click on the the second option, ie POST Add Media file
  • in the Body
    • for the Path if you put for example
      • /banners/joomlacamp26.png the media will be uploaded in the /banner/ folder… under the /files/ folder
      • local-images:/banners/joomlacamp26.png the media will be uploaded in the /banner/ folder… under the /images/ folder
    • for the Content you should put your media file in base64 format. Example for our demo:
      • go to an online base64 converter like https://www.base64-image.de
      • upload your image
      • click on the </> Code button
      • copy the result… but without the initial data:image/svg+xml;base64,
      • paste

post_media1

4.12.1 Want to make a quick test?

Use the following Body where the Content is a tiny image:

{
  "path": "local-images:/banners/tiny.png",
  "content": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"
}

You will then see this tiny image in your (8x8 pixels ) /banners folder:

post_media2

4.12.2 What if the file already exists?

If the file already exists when you POST you will get a 400 error:

{
  "errors": [
    {
      "title": "File exists and overwriting not requested: /banners/joomlacamp26.png",
      "code": 400
    }
  ]
}

As explained by Herman Peeren:

A POST request only adds a new file, cannot overwrite an existing file. When doing a POST, the overridestate parameter of the model is set to false (in the add() method in the API Controller of com_media). If you want to overwrite an existing file, you’ll have to use a PATCH request (which sets the override state parameter of the model to true (in the edit() method in the controller). But in order to use that PATCH, the resource has to exist.

This is pretty much the same for all POST and PATCH requests, also for other resources. So you always get a sequence when adding a file:

  • does the file already exist?
  • if not, then do a POST.
  • if it does exist, then do a PATCH.

But the same holds for adding an Article (Alias must not exist), Category (idem), User (Username and Email must not already exist), etc. So you always have this same sequence. One of the things an MCP-server offers on top of just the API is this sequence. And you’ll encounter it in all automation. And a step further: when creating an Article, you first have to check if the Category or User or whatever other entity is mentioned in the Article exists, and if not then create that other “dependent” resource first.

4.13 A DEL request to delete an Article

Similarly to the workflow when deleting an Article from the Backend, in order to delete an Article via the Web Services

  • one must first Patch it with State on -2 (ie Trashed) in order to put it in Trash
  • and only then that Article can be really deleted

5 Not all Web Services are in Joomla yet

You might notice that this Collection has for example Guided Tours.

But at the moment (ie J!6.0) there is no Web Service for Guided Tours.

In order to see all the already available Web Services (endpoints) in Joomla

  • go to the backend of your Joomla website
  • click on System > Plugins
  • filter on Type = webservices

Note: in the available Web Services one could choose to enable only some of them.

6 Go further with Bruno

A few extra information before letting you play with Bruno.

Just to keep it short, let’s only mention the 3 buttons at the top right

  • the </> icon allows to Generate Code, for example in PHP, Javascript etc
  • the 🖫 icon allows to Save the changes you made to a given item of the Collection. But alternatively, if you are playing and you want to keep the “original” intact, you could also simply click on the ... icon next to the Action in order to Clone it
  • the -> icon allowing to execute the current request (keyboard shortcut: CTRL+ENTER)

Last but not least: in Bruno each item of the Collection a simple JSON file (with .bru extension), meaning for example that

  • you can easily edit them in your IDE like VS Code or PHPStorm for example
  • you have them available locally (unlike Postman)
  • you can put them in a Git repo, work in team etc

7 Bruno directly in VS Code

With Bruno for VS Code extension, you can develop and test your APIs in Bruno right from Visual Studio Code. You can use the VS Code extension to open .bru files, send API requests, manage collections and environments, and much more. Streamline your development workflow by testing your APIs in the same application you use to develop them.

https://marketplace.visualstudio.com/items?itemName=bruno-api-client.bruno

8 Using directly PHPStorm as API Client

See this useful presentation made by Roland Dalmulder:

https://github.com/roland-d/joomla-phpstorm-api

9 Summary

 

Note: this article is work in progress. Please contact me for any feedback/suggestion or if you know of any other interesting resource about this topic! I will be happy to integrate them in the current article which will also be used for presentations different Joomla events in order to spread the knowledge and share experiences.

Some articles published on the Joomla Community Magazine represent the personal opinion or experience of the Author on the specific topic and might not be aligned to the official position of the Joomla Project

2
The March Issue
 

Comments

Already Registered? Login Here
No comments made yet. Be the first to submit a comment

By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/