Skip to main content

Banners in Joomla

09 June 2026

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.

ConceptWhat it isDatabase 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:

TypeWhat you provideWhere 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.

Back to top

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:

FieldPurpose
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:

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

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:

OptionWhat 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:

  1. Set Keywords on the banner, for example joomla, hosting.
  2. Enable Search by Keywords on the module.
  3. 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 layerEffect 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.

Back to top

4. Tracking and Limits

4.1 Impressions versus Clicks

Joomla records two metrics, and it is worth keeping them clearly apart:

MetricWhen it incrementsColumn
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 reset date) schedules when impmade and clicks are 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 top

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

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

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:

ConcernWhy it happensMitigation
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.

Back to top

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 impmade reached imptotal? (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.
Back to top

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_tracks periodically on busy sites.
Back to top

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 top

11. 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
Banners in Joomla
Peter Martin
Peter Martin

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