Banners in Joomla
Most Joomla users know the Banners component exists, but few realise what it actually is. It is not just a place to upload a picture. It is a small, complete, self-hosted ad server that lives inside your CMS.
With com_banners you can rotate promotions, sponsors, and house ads without editing your template. You can count how many times each banner is shown and clicked, using your own first-party data. You can cap delivery so a client gets exactly the views they paid for. You can target banners by keyword, category, or language. And you can do all of this without a third-party ad network, without an external script, and without cookies by default.
From "show a banner" to keyword targeting, impression caps, and click tracking.
This article explains how Joomla Banners really work. It covers the basics for website owners, the practical setup for administrators, and the technical details for developers. You will learn how clients, banners, and categories fit together, how the module delivers and rotates them, how tracking and impression caps work, and how to avoid the most common mistakes.
1. The Basics
1.1 The Three Building Blocks
The whole component rests on three simple concepts. Once you understand how they relate, everything else falls into place.
| Concept | What it is | Database table |
|---|---|---|
| Client | The advertiser or sponsor who owns banners. | #__banner_clients |
| Banner | A single creative (an image or custom HTML). | #__banners |
| Category | A group of banners (a com_banners category). | #__categories |
The relationship is easy to remember: a banner belongs to one category and, optionally, to one client.
1.2 How the Parts Fit Together
Here is the mental model:
Client ─┐
├─ Banner ─ Category
Tracks ─┘ (creative) (grouping)
- Banner is the thing visitors actually see.
- Client is who the banner is for, and it holds the default tracking settings.
- Category is how you group banners so a module can pull from them.
- Tracks is the log of impressions and clicks.
1.3 Where to Find It
In the Joomla 6 backend, the component lives under the Components menu:
Components → Banners → Banners (the creatives)
Components → Banners → Categories (grouping)
Components → Banners → Clients (advertisers)
Components → Banners → Tracks (impression / click log)
The front-end display is handled separately, by a module:
System → Site Modules → New → Banners (mod_banners)
Remember this split: the component stores and manages the data, the module shows it on the website.
1.4 Two Kinds of Banner
On the banner edit screen, the Type field decides everything else:
| Type | What you provide | Where it is stored |
|---|---|---|
| Image | An image file, a click URL, and width / height / alt. | params (imageurl, width, …) |
| Custom | Raw HTML, a <script>, or an <iframe>. |
custombannercode |
With an Image banner, Joomla builds the <a><img></a> markup for you. With a Custom banner, you control the markup completely, which is great for responsive ads or third-party tags. As we will see in section 8, this freedom also comes with a security cost.
2. Creating Banners
2.1 Create a Client First
It is good practice to create the client before the banner, even for your own house ads. Go to:
Components → Banners → Clients → New
Fill in the client details:
- Name, Contact, Email, and Extra info.
- Purchase type: Unlimited, Yearly, Monthly, Weekly, or Daily.
- Track impressions and Track clicks: the default tracking for all of this client's banners.
The client sets the defaults. Each individual banner can still override them.
2.2 Create the Banner
Now create the creative:
Components → Banners → Banners → New
Two fields are required to start:
- Name: an internal label for your own reference.
- Category: which com_banners category the banner lives in.
Then choose the Type (Image or Custom) and fill in the creative.
2.3 The Banner Edit Screen: Key Fields
These are the fields you will use most often:
| Field | Purpose |
|---|---|
| Type | Image or Custom HTML. |
| Click URL | Where the click lands (for image banners). |
| Image | The creative itself (for image banners). |
| Custom Code | Raw HTML (for custom banners). |
| Width / Height / Alt | Image rendering and accessibility. |
| Sticky | Force this banner to the top of the rotation. |
| Max Impressions | A hard cap (imptotal); 0 means unlimited. |
2.4 Targeting Fields
A second group of fields controls when and where a banner is eligible to appear:
| Field | Effect |
|---|---|
| Keywords | Matched against the page keywords for targeted display. |
| Use Own Prefix | Custom keyword-prefix matching for this banner. |
| Category | A module can pull "all banners in category X". |
| Language | Show only on a matching site language. |
| Client | Inherits the client's tracking and purchase defaults. |
3. Displaying Banners with mod_banners
3.1 The Module Is the Delivery Engine
This is the single most important thing to understand: a banner does nothing until a Banners module renders it. The component stores the data, but mod_banners selects and shows it.
Banner (data) → mod_banners (selection + render) → module position
Create the module under System → Site Modules → New → Banners, then assign it to a position and to the menu items where it should appear.
3.2 Module Options
The module options decide which banners are eligible and how they look:
| Option | What it does |
|---|---|
| Target | Link target: same window, new window, or popup. |
| Count | How many banners to show at once (default 5). |
| Client | Restrict to one client's banners. |
| Category | Restrict to one or more categories. |
| Search by Keywords | Match the page's meta keywords. |
| Randomise | Sticky-ordered, or sticky-then-random. |
| Header / Footer text | Wrap the banner block with text. |
3.3 Ordering and Rotation
The selection query orders the banners in one of two ways, depending on the Randomise setting:
ORDER BY sticky DESC, ordering -- "Sticky Ordering"
ORDER BY sticky DESC, RAND() -- "Sticky, Randomise"
The logic is straightforward:
- Sticky banners always come first.
- Within the same level of stickiness, you choose a fixed order or a random one.
- Count then limits how many of those banners are actually rendered.
3.4 Keyword Targeting in Practice
Keyword targeting lets you show relevant banners per page without creating a separate module for each one. It takes three steps:
- Set Keywords on the banner, for example
joomla, hosting. - Enable Search by Keywords on the module.
- On a page whose meta keywords include
hosting, that banner becomes eligible.
One module can then serve different banners on different pages, based on what each page is about.
3.5 Template Overrides
The module ships with a single layout that you can fully override. This is the correct place to change how banners look:
modules/mod_banners/tmpl/default.php ← the core layout
templates/your_template/html/mod_banners/default.php ← your override
Use an override to build things the core layout does not offer:
- Sponsor grids or Bootstrap cards.
- Sliders and carousels.
- Lazy-loaded, responsive
<picture>markup.
The principle is the same as everywhere else in Joomla: data and presentation stay separate. You override the view, not the model. One tip: the core layout already emits rel="noopener noreferrer" on new-tab links. Keep that when you override, for security.
3.6 Caching versus Impression Accuracy
This is a subtle but important point. An impression is a dynamic write on every render. Caching, by design, avoids re-rendering. So caching and accurate counting work against each other.
| Cache layer | Effect on counts |
|---|---|
| Module cache | A cached module is not re-rendered, so impmade does not increment. |
| Page / System cache | The whole page is served from cache, so no banner code runs. |
| Reverse proxy / CDN | The edge serves the HTML, so Joomla never sees the view. |
The more aggressively you cache, the more impmade undercounts real views. If you need accurate counts, exclude banner modules from the cache. Otherwise, accept that the counts are a floor, not a total.
4. Tracking and Limits
4.1 Impressions versus Clicks
Joomla records two metrics, and it is worth keeping them clearly apart:
| Metric | When it increments | Column |
|---|---|---|
| Impression | The banner is rendered on a page. | impmade |
| Click | A visitor clicks through. | clicks |
Both are also logged per day into #__banner_tracks, when tracking is on. The real value is in the ratio between them, the click-through rate:
CTR = clicks / impressions × 100 (%)
A banner with many impressions but few clicks is underperforming.
4.2 The Impression Cap
Each banner can have a hard limit on how many times it is shown:
imptotal = the cap (0 = unlimited)
impmade = how many times shown so far
The selection query only picks banners that still have room:
imptotal = 0 OR impmade < imptotal
When impmade reaches imptotal, the banner stops appearing automatically. This is how you deliver exactly the number of views a client paid for.
4.3 How a Click Is Counted
When a visitor clicks an image banner, Joomla does not send them straight to the destination. It routes the click through the component first:
/index.php?option=com_banners&task=click&id=42
→ clicks = clicks + 1
→ log a row in #__banner_tracks (track_type = 1)
→ 301 redirect to the real Click URL
The redirect is invisible to the visitor, but it gives Joomla the chance to count the click before sending the visitor on their way.
4.4 The Tracking Decision Chain
Whether a click or impression is actually logged is decided level by level, from most specific to most general:
Banner.track_clicks (-1 = inherit)
└─ Client.track_clicks (-1 = inherit)
└─ com_banners global config: track_clicks
A value of -1 means "inherit from the level above". Impressions follow exactly the same pattern. This is the same inheritance idea you may know from category permissions.
4.5 Track Types
The #__banner_tracks table is a compact daily aggregate, not a list of individual events:
track_date datetime (rounded to the hour: Y-m-d H:00:00)
track_type 1 = click, 2 = impression
banner_id which banner
count how many in that hour
There is one row per banner, per type, per hour, not one row per event. This is a deliberate design choice that keeps the table small even on a busy site.
4.6 Reset and Purchase Type
Two more fields model the billing side of paid placements:
- Reset (the
resetdate) schedules whenimpmadeandclicksare zeroed out. - Purchase type (Unlimited, Yearly, Monthly, Weekly, or Daily) models the billing cycle.
Purchase type is resolved with the same inheritance chain as tracking:
Banner.purchase_type (-1 = inherit)
└─ Client.purchase_type (-1 = inherit)
└─ Global config purchase_type
Back to top5. Security of Custom Banners
5.1 Custom HTML Is Stored Raw
This section deserves its own place because it is the one thing people overlook. Custom HTML banners are stored exactly as you type them. Joomla does not sanitise them.
custombannercode → filter="raw" (in banner.xml)
clickurl → filter="url" (URL-validated)
The click URL is validated, but the markup of a custom banner is not.
5.2 Why This Matters
Because custom code is raw, a banner author who can inject a <script> tag creates stored cross-site scripting (XSS) on every page where the module appears. The consequences are practical:
- Only let trusted user groups create custom banners. Gate it with ACL (see section 6).
- Prefer Image banners for creatives supplied by advertisers.
- Reserve Custom banners for your own staff.
A simple way to remember it: image banners are safe by construction, while custom banners trade safety for control. The redirect validates the destination URL, but the markup is your responsibility.
Back to top6. Categories and Access Control
6.1 Banners Use the Shared Category System
Banner categories are not special. They are ordinary Joomla categories that happen to belong to the banners component:
#__categories WHERE extension = 'com_banners'
This means banner categories use the same nested tree, the same Rebuild button, and the same ACL machinery as article categories. If you already understand article categories, you already understand banner categories.
6.2 Why Categories Matter Here
Categories are the link between your banners and the module that displays them:
- A module targets a category, so all banners in it become a rotation pool.
- You can organise by campaign, by placement, or by sponsor.
- Permissions inherit down the category tree (Create, Edit, Edit State, and so on).
If you want the detail on how the nested tree stores lft and rgt values, see the separate Categories in Joomla article.
6.3 Access and Language
- Access level on the banner controls who can see it. This is rarely needed, because the module already controls display.
- Language lets a banner appear only on its matching site language.
- On a multilingual site, you can keep one rotation pool and let the language filter pick the right creatives.
7. Under the Hood (Developer View)
7.1 Three Tables
The entire component is built on three dedicated tables:
#__banners the creatives + counters + targeting
#__banner_clients advertisers + default tracking / purchase
#__banner_tracks hourly aggregate of clicks / impressions
Plus rows in the shared #__categories table, where extension = com_banners.
7.2 Key Columns in #__banners
The main table carries the creative, the counters, and the targeting all in one place:
type 0 = image, 1 = custom HTML
custombannercode raw markup for custom banners
params JSON: imageurl, width, height, alt, ...
cid client id (→ #__banner_clients)
catid category id (→ #__categories)
imptotal / impmade impression cap / count
clicks click count
sticky float to top of rotation
state published / unpublished / archived / trashed
metakey keywords used for targeting
own_prefix / metakey_prefix custom prefix matching
track_clicks / track_impressions -1 inherit, 0 off, 1 on
purchase_type -1 inherit, else billing cycle
reset when counters reset
language '' = all, or a language tag
7.3 The MVC Layout
The code is split across three locations, following Joomla's standard structure:
administrator/components/com_banners/ ← manage banners, clients, tracks
src/Model, src/Table, src/Controller, tmpl/, forms/
components/com_banners/ ← front-end click + selection
src/Model/BannersModel.php (selection + impressions)
src/Model/BannerModel.php (single banner + click tracking)
src/Controller/DisplayController.php
modules/mod_banners/ ← renders the chosen banners
src/Helper/BannersHelper.php
tmpl/default.php
7.4 The Selection Query
BannersModel::getBannerQuery() builds roughly this query to pick eligible banners:
SELECT a.*
FROM #__banners AS a
LEFT JOIN #__banner_clients AS cl ON cl.id = a.cid
LEFT JOIN #__categories AS cat ON cat.id = a.catid
WHERE a.state = 1
AND (a.publish_up IS NULL OR a.publish_up <= :now)
AND (a.publish_down IS NULL OR a.publish_down >= :now)
AND (a.imptotal = 0 OR a.impmade < a.imptotal)
AND cl.state = 1
[AND a.catid IN (:categories)]
[AND keyword/metakey REGEXP conditions]
ORDER BY a.sticky DESC, a.ordering -- or RAND()
This single query explains most of the behaviour described earlier: state, the publish window, the impression cap, the client state, and the ordering all live here.
7.5 Keyword Matching Is Real SQL
When keyword search is on, Joomla matches each page keyword with a word-boundary regular expression against the banner, client, and category metakeys:
$regexp = $db->getServerType() === 'mysql'
? '\\b' . $keyword . '\\b' // MySQL / MariaDB
: '[[:<:]]' . $keyword . '[[:>:]]'; // PostgreSQL
There is also the own-prefix logic: a banner can match a prefix of a keyword rather than the whole word, which allows fine-grained targeting without exact matches.
7.6 Custom Banner Code Tokens
For custom banners, the custombannercode can contain placeholders that mod_banners replaces at render time:
{CLICKURL} → the tracked click-through URL
{NAME} → the banner name
This is a neat detail: it lets a hand-written <a> or <script> creative still flow through Joomla's click tracking, so you do not lose your statistics just because you wrote the markup yourself.
7.7 Programmatic Access
The same nested-set Categories API that serves articles also serves banners:
use Joomla\CMS\Categories\Categories;
$categories = Categories::getInstance('Banners');
$node = $categories->get('sponsors'); // by alias or id
$children = $node->getChildren();
The table and model classes live under predictable namespaces:
Joomla\Component\Banners\Administrator\Table\BannerTable
Joomla\Component\Banners\Site\Model\BannersModel
7.8 Web Services API (Headless)
The component ships a REST API, so banners and clients are reachable headlessly:
api/components/com_banners/
src/Controller/BannersController.php
src/Controller/ClientsController.php
src/View/Banners, src/View/Clients
GET /api/index.php/v1/banners
GET /api/index.php/v1/banners/clients
This opens the door to modern architectures, where the data lives in Joomla but the delivery happens elsewhere:
Joomla API → React / Vue front-end → custom banner rendering
One caveat: API delivery bypasses mod_banners, so you have to count impressions and clicks yourself.
7.9 Behaviour at Scale
The model is simple, but a few things bite on large installs:
| Concern | Why it happens | Mitigation |
|---|---|---|
| Write contention | Every view or click is an UPDATE on #__banners. |
Accept hourly aggregation; do not disable it. |
| Table growth | #__banner_tracks gets one row per banner, type, and hour. |
Archive or prune old rows periodically. |
| Selection cost | Keyword REGEXP plus joins across clients and categories. |
Index state / catid / language; keep keyword lists lean. |
| Huge rotation pool | 100k banners is a large pool to select from. | Filter hard by category or client in the module. |
Tracking is designed to stay small through hourly buckets. The main long-term maintenance job is archiving #__banner_tracks.
8. Common Mistakes and Pitfalls
8.1 "My Banner Does Not Show"
This is the most common support question. Work through the checklist in order:
- Is the banner published? Is the client published?
- Is it inside its publish_up / publish_down window?
- Has
impmadereachedimptotal? (The cap is reached.) - Is the module assigned to this menu item and position?
- Is the module's Category / Client filter excluding it?
- Is keyword search on, but the page has no matching keywords?
8.2 Tracking and Privacy Gotchas
- Tracking is off unless you enable it at the banner, client, or global level.
- Counts aggregate per hour, so do not expect per-second precision.
- Clicks go through a redirect, so ad blockers or a strict CSP can interfere with custom code.
- Reset dates silently zero the counters. Document them so nobody is surprised.
8.3 Image versus Custom Mistakes
- Switching the Type does not migrate data. Image fields and custom code are stored separately.
- Forgetting Width / Height / Alt on image banners hurts layout and accessibility.
- Custom HTML bypasses Joomla's
<img>builder, so you own both responsiveness and security.
9. Best Practices
If you only remember a few things from this article, remember these:
- Create the client first, then the banner, then the module that shows it.
- Prefer image banners for advertiser creatives; reserve custom banners for trusted staff.
- Use the impression cap to deliver exactly what a client paid for.
- Exclude banner modules from cache if you need accurate impression counts.
- Use categories to group banners into rotation pools by campaign or sponsor.
- Archive
#__banner_tracksperiodically on busy sites.
10. Quick Reference
CLIENT Components → Banners → Clients → New
BANNER Components → Banners → Banners → New
DISPLAY Site Modules → New → Banners (assign position + menu)
CATEGORY Components → Banners → Categories (extension=com_banners)
CAP Max Impressions (imptotal); 0 = unlimited
STICKY Sticky = float to top of rotation
TARGET Keywords + module "Search by Keywords"
TRACK Banner → Client → Global (-1 = inherit)
TRACKS LOG #__banner_tracks (type 1=click, 2=impression, hourly)
CLICK FLOW task=click&id=X → count → redirect to Click URL
OVERRIDE templates/{tpl}/html/mod_banners/default.php
CTR clicks / impressions × 100
SECURITY custombannercode = raw → trusted groups only
API /api/index.php/v1/banners (headless)
TABLES #__banners, #__banner_clients, #__banner_tracks
Back to top11. Summary
Joomla Banners is a complete, self-hosted ad server hiding in plain sight. Once you see it that way, the component stops looking like a simple image uploader and starts looking like a small, capable system.
It gives you:
- Delivery: rotation, sticky priority, count, and randomisation.
- Targeting: by keyword, category, client, and language.
- Limits: impression caps, scheduling, and billing cycles.
- Analytics: first-party impression and click tracking.
- Integration: the shared Categories tree, ACL, and a clean MVC model.
- Privacy: no external network and no cookies by default.
The data lives in Joomla, but the delivery can live anywhere, from a classic module to a headless React front-end. The one rule to keep in mind throughout is the separation of concerns: the component holds the data, the module renders it, and the template override controls how it looks.
If you are running paid placements, house ads, or sponsor rotations, and you want them counted, capped, and targeted without a third-party network, Joomla Banners already has what you need. The hard part is not the feature set. It is knowing it is there.
Back to top

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


