---
title: "Traffic Boost Indexing | IndexNow SEO Plugin for Joomla Websites"
description: "Push new Joomla URLs to search engines with IndexNow and Google Search Console. Automate submissions and track indexing with detailed logs and retries."
url: "https://www.yoozecase.com/?item_id=20"
date: "2026-10-08T06:45:56+00:00"
language: "en-GB"
---

#  Traffic Boost Indexing

Stop waiting to be indexed!

**Latest Version**
3.2.0
**Requires**
Joomla 5+, PHP 8.1 or newer
**Release date**
05 October 2026
**Made for**
Joomla
**License**
GPL-2.0-or-later
**Price**
€25.00 (incl. 6 months of updates)

 [ 🛒 Get it for Joomla 👍 ](https://yoozecase.lemonsqueezy.com/checkout/buy/2fa37499-a59d-4cfb-af21-6e400ff5ead4)

 [ Demo page ](https://www.yoozecase.com/traffic-boost-indexing-demo)

Push new Joomla URLs to search engines instantly with IndexNow, Google Search Console support, automatic submissions and detailed logs.

##  What's new in version 3.2.0

- **New:** License tab: enter the key from your order email and click Activate. From the next release on, Joomla installs updates of Traffic Boost Indexing only on sites with an active key; this update itself still reaches every site. Without a key the plugin keeps working, you only stop receiving updates.
- **New:** Deactivate frees the key for another site; uninstalling the plugin does the same. The key is checked once a day while an administrator works in the backend.
- **Changed:** Messages in the dashboard and the admin module now appear in the language of your administrator, in all six languages.
- **Changed:** The update site is called "Traffic Boost Indexing Updates" and is linked to the package again after an uninstall and reinstall.
- **Changed:** Help texts in the settings state the default value and explain the ZOO options and where to find the Google client secret.
- **Changed:** Links to documentation and support point to the current product page; the Zoo tag note links to Amazing Zoo Tag SEF.
- **Fixed:** One French help text in the Search Console steps was cut off.

 ![Traffic Boost Indexing 1](https://www.yoozecase.com/files/traffic-boost-indexing_1.png)

 [](#)

 [](#)

---

##  Description

Get your Joomla content discovered faster with Traffic Boost Indexing. Automatically notify search engines when you publish or update articles, categories, tags, media and ZOO content.

Powered by IndexNow with Google Search Console integration, automatic retries and detailed submission logs. No manual URL submissions, no cron jobs and no extra work for editors.

##  Feature request

 ### What is the next feature for this plugin?

Submit your idea or vote on existing ones.

  #### Top voted

   Website (leave empty)

'; } html += ''; if (oc.title) { html += '##### ' + escapeHtml(oc.title) + '

'; } if (oc.text) { html += '' + escapeHtml(oc.text).replace(/\\n/g, '
') + '

'; } html += '

'; } // Auto winner(s) - top N from the already vote-sorted features if (mode === 'auto_winner' || mode === 'both') { var n = oc.winnerCount | 0; if (n '; for (var i = 0; i ' + (w.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(w.image_url)%20+%20')

' : '') + '' + (i + 1) + '' + '' + escapeHtml(w.title) + '' + '' + getIcon(this.cfg.voteIcon) + ' ' + (w.votes | 0) + '' + ''; } html += ''; } } if (html === '') { this.elOutcome.hidden = true; return; } this.elOutcome.innerHTML = html; this.elOutcome.hidden = false; }; /** * Roadmap view (v3.2.0): renders the items as status columns, in the * canonical status order from cfg.statuses. Each column is an 12. inside \* the existing .yc-fb-list , holding its own inner  of items - \* this keeps the delegated click/change handlers on elList working \* unchanged. Unknown statuses fall back into the "open" column so no item \* can silently disappear. Empty columns are skipped when \* cfg.roadmapHideEmpty is set. \*/ Board.prototype.renderRoadmap = function (items) { var stats = (this.cfg.statuses && this.cfg.statuses.length) ? this.cfg.statuses : \['open', 'planned', 'in\_progress', 'shipped', 'declined'\]; var labels = this.cfg.statusLabels || {}; var groups = {}; var i, st; for (i = 0; i ' + '##### ' + '' + escapeHtml(labels\[sv\] || sv) + '' + arr.length + '' + '

    ' + '' + itemsHtml + '
    ' + '
'; } return html; }; Board.prototype.renderItem = function (f, index) { var disabled = this.cfg.isClosed ? 'disabled' : ''; var voted = f.voted ? ' yc-voted' : ''; var descHtml = f.description ? '' + escapeHtml(f.description).replace(/\\n/g, '
') + '

' : ''; var thumbHtml = f.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

' : ''; // Status workflow (v3.0.0): badge for any non-default status. The // roadmap view (v3.2.0) skips the badge - the column already // communicates the status, a badge would be redundant noise. var st = (f.status || 'open'); var labels = this.cfg.statusLabels || {}; var statusBadge = (!this.isRoadmap && st && st !== 'open' && labels[st]) ? '' + escapeHtml(labels\[st\]) + '' : ''; // Design themes (v3.1.0): arena / neon / pixel render extra per-item // furniture - rank number, avatar (image or initials), a "leader" pill // on rank 1, and a "votes" label. Classic/colour themes are untouched. var t = this.cfg.theme || 'light'; var themed = (t === 'arena' || t === 'neon' || t === 'pixel'); var rank = (typeof index === 'number') ? (index + 1) : 0; var rankHtml = '', leaderHtml = '', avatarHtml = '', votesLabel = ''; if (themed) { rankHtml = '' + pad2(rank) + ''; if (rank === 1) { leaderHtml = '' + escapeHtml(this.cfg.leaderLabel || 'Leader') + ''; } if (f.image_url) { avatarHtml = '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

'; } else { avatarHtml = '' + escapeHtml(initialsOf(f.title)) + '

'; } votesLabel = '' + escapeHtml(this.cfg.votesLabel || 'votes') + ''; } // Moderation overlay: subtle corner buttons, only when canModerate. // Buttons appear on hover/focus (styled in CSS). Even in customizer // preview we render them when canModerate is true so the moderator // can see what visitors with moderation rights will see. var modHtml = ''; if (this.cfg.canModerate) { var parts = []; var hideTitle = escapeHtml(this.t('hideTitle', 'Hide this feature')); var deleteTitle = escapeHtml(this.t('deleteTitle', 'Delete this feature')); if (this.cfg.allowSoftHide) { parts.push( '' ); } if (this.cfg.allowHardDelete) { parts.push( '' ); } if (parts.length || (this.cfg.statuses && this.cfg.statuses.length)) { var sParts = parts.slice(); if (this.cfg.statuses && this.cfg.statuses.length) { var opts = ''; for (var si = 0; si ' + escapeHtml(sl) + ''; } var setStatus = escapeHtml(this.t('setStatus', 'Set status')); sParts.push( '' ); } modHtml = '' + sParts.join('') + '

'; } } var mediaHtml = themed ? avatarHtml : thumbHtml; return ( '32. ' + leaderHtml + rankHtml + '' + mediaHtml + '' + '##### ' + escapeHtml(f.title) + '

    ' + statusBadge + descHtml + '

    ' + modHtml + '
' ); }; /** * Detects whether a JSON response looks like a CSRF/token rejection. * Two cases: * 1. com_ajax-level rejection (Joomla's auto-token-check on POST): top * level message contains the JINVALID_TOKEN_NOTICE text and HTTP * status is typically 403. * 2. Plugin-level rejection from requireToken(): payload.error === "Invalid token". */ Board.prototype.isTokenError = function (status, raw, payload) { if (status === 403) return true; var hay = ''; try { hay = JSON.stringify(raw || {}) + '|' + JSON.stringify(payload || {}); } catch (e) {} return /invalid (security )?token/i.test(hay); }; /** * Fetch a fresh CSRF token from the server. Updates this.cfg.csrfToken so * the next call (and any future calls in this Board's lifetime) use the * correct one. Cache-bust with a timestamp so no upstream CDN caches the * response. */ Board.prototype.fetchFreshToken = function () { var self = this; var url = this.cfg.ajaxUrl + '&task=token&_=' + Date.now(); return fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Cache-Control': 'no-cache' } }) .then(function (r) { return r.json(); }) .then(function (data) { var p = self.unwrapAjax(data); if (p && p.ok === true && typeof p.token === 'string' && p.token.length >= 16) { self.cfg.csrfToken = p.token; return p.token; } throw new Error('No fresh token in response'); }); }; /** * Generic POST-with-token helper. Builds FormData from a plain object, * adds the current token, posts. On 403 / token-error response, fetches a * fresh token and retries exactly once. * * @param {string} task The task name (appended to URL and body) * @param {object} fields Key-value form fields (no token) * @return {Promise} Resolves to the unwrapped payload on success. \* Rejects with Error containing .reason on failure. \*/ Board.prototype.postWithToken = function (task, fields) { var self = this; function attempt(token) { var fd = new FormData(); fd.append('task', task); for (var k in fields) { if (Object.prototype.hasOwnProperty.call(fields, k)) { fd.append(k, fields\[k\]); } } fd.append(token, '1'); return fetch(self.cfg.ajaxUrl + '&task=' + encodeURIComponent(task), { method: 'POST', credentials: 'same-origin', body: fd, headers: { 'Accept': 'application/json' } }).then(function (r) { return r.json().then(function (data) { return { status: r.status, raw: data, payload: self.unwrapAjax(data) }; }); }); } return attempt(this.cfg.csrfToken).then(function (res) { // Success path if (res.payload && res.payload.ok === true) { return res.payload; } // Token rejection? Fetch fresh and retry once. if (self.isTokenError(res.status, res.raw, res.payload)) { return self.fetchFreshToken().then(function (fresh) { return attempt(fresh).then(function (res2) { if (res2.payload && res2.payload.ok === true) { return res2.payload; } var err = new Error( (res2.payload && res2.payload.error) || (res2.raw && res2.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res2.payload || null; throw err; }); }); } // Some other error - surface it var err = new Error( (res.payload && res.payload.error) || (res.raw && res.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res.payload || null; throw err; }); }; Board.prototype.handleSubmit = function () { var self = this; if (isInBuilder()) { this.showStatus(this.t('previewSubmit', 'Preview mode - submissions are disabled in the YOOtheme builder. Try this on the published page.'), 'info'); return; } if (this.cfg.isClosed) { this.showStatus(this.cfg.closedMessage || this.t('votingClosed', 'Voting is closed.'), 'info'); return; } var title = (this.elTitle && this.elTitle.value || '').trim(); var desc = (this.elDesc && this.elDesc.value || '').trim(); // Client-side validation: minimum title length. Server re-validates. var minLen = (this.cfg.minTitleLength | 0) || 2; if (title.length for hide/delete, remember parent + sibling for rollback. var li = btn.closest('li'); var parentEl = li ? li.parentNode : null; var nextEl = li ? li.nextSibling : null; if (li && (action === 'hide' || action === 'delete')) { li.remove(); } btn.disabled = true; var task = action === 'hide' ? 'moderate\_hide' : action === 'delete' ? 'moderate\_delete' : action === 'restore' ? 'moderate\_restore' : ''; if (!task) { btn.disabled = false; return; } this.postWithToken(task, { feature\_id: featureId }) .then(function (payload) { // Success - keep the UI as-is (already removed) and refresh // so vote counts, top box, and hidden-features panel reflect // the new state. self.refresh(); if (self.hiddenOpen) { self.hiddenLoaded = false; // force a refetch self.fetchHiddenFeatures(); } var msg = action === 'delete' ? self.t('deleted', 'Feature deleted.') : action === 'hide' ? self.t('hidden', 'Feature hidden.') : action === 'restore' ? self.t('restored', 'Feature restored.') : self.t('done', 'Done.'); self.showStatus(msg, 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { // Rollback: re-insert the row if we removed it. if (li && parentEl) { if (nextEl) { parentEl.insertBefore(li, nextEl); } else { parentEl.appendChild(li); } } btn.disabled = false; self.showStatus(err && err.message ? err.message : self.t('modFailed', 'Moderation action failed.'), 'error'); }); }; Board.prototype.handleStatusChange = function (featureId, status, sel) { var self = this; if (!featureId || !status) return; sel.disabled = true; this.postWithToken('moderate\_status', { feature\_id: featureId, status: status }) .then(function () { self.refresh(); var label = (self.cfg.statusLabels && self.cfg.statusLabels\[status\]) ? self.cfg.statusLabels\[status\] : status; self.showStatus(self.tf('statusSet', 'Status: %s', label), 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { sel.disabled = false; self.showStatus(err && err.message ? err.message : self.t('statusFailed', 'Could not set status.'), 'error'); }); }; /\*\* \* Toggle the hidden-features panel open/closed. First open triggers \* an AJAX fetch; subsequent toggles just hide/show the cached list. \*/ Board.prototype.toggleHiddenPanel = function () { if (!this.elHiddenList || !this.elHiddenLabel) return; var arrow = this.elHiddenToggle && this.elHiddenToggle.querySelector('.yc-fb-hidden-arrow'); if (this.hiddenOpen) { this.hiddenOpen = false; this.elHiddenList.hidden = true; this.elHiddenLabel.textContent = this.t('showHidden', 'Show hidden features'); if (arrow) arrow.classList.remove('yc-open'); return; } this.hiddenOpen = true; this.elHiddenList.hidden = false; this.elHiddenLabel.textContent = this.t('hideHidden', 'Hide hidden features'); if (arrow) arrow.classList.add('yc-open'); if (!this.hiddenLoaded) { this.fetchHiddenFeatures(); } }; /\*\* \* Fetch the hidden-features list via the moderator-only endpoint. \* Re-callable to refresh the list after a restore. \*/ Board.prototype.fetchHiddenFeatures = function () { var self = this; if (!this.elHiddenList) return; this.elHiddenList.innerHTML = '2. ' + escapeHtml(this.t('loading', 'Loading...')) + '
'; var url = this.cfg.ajaxUrl + '&task=moderate\_list\_hidden&board\_id=' + encodeURIComponent(this.cfg.boardId); fetch(url, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } }) .then(function (r) { return r.json(); }) .then(function (data) { var payload = self.unwrapAjax(data); if (!payload || payload.ok !== true) { self.elHiddenList.innerHTML = '4. ' + escapeHtml((payload && payload.error) ? payload.error : self.t('hiddenLoadFailed', 'Failed to load hidden features.')) + '
'; return; } self.hiddenLoaded = true; var features = payload.features || \[\]; if (features.length === 0) { self.elHiddenList.innerHTML = '6. ' + escapeHtml(self.t('noHidden', 'No hidden features.')) + '
'; return; } var html = ''; for (var i = 0; i ' + escapeHtml(f.description).replace(/\\n/g, '
') + '' : ''; var restoreTitle = escapeHtml(self.t('restoreTitle', 'Restore this feature')); html += '9. ' + '' + '##### ' + escapeHtml(f.title) + '

    ' + descHtml + '' + escapeHtml(self.tf('votesMeta', '%s votes', f.votes | 0)) + '' + '

    ' + '' + '
'; } self.elHiddenList.innerHTML = html; }) .catch(function () { self.elHiddenList.innerHTML = '11. ' + escapeHtml(self.t('hiddenNetwork', 'Network error while loading hidden features.')) + '
'; }); }; function initAll(scope) { var roots = (scope || document).querySelectorAll('.yc-fb-root'); for (var i = 0; i ---

##  Changelog

---

 [ v3.2.0 05/10/2026 ](#) Added- License tab. Enter the key from your order email and click Activate. From the next release on, Joomla installs updates of Traffic Boost Indexing only on sites with an active key; this update itself still reaches every site. Without a key the plugin keeps working as before, you only stop receiving updates. Deactivate frees the key for use on another site, and uninstalling the plugin does the same.

- The key is checked once a day while an administrator works in the backend. Only the key, the site address and the versions of the plugin, Joomla, PHP and YOOtheme Pro go to the YOOzeCase license server; NOTICE.txt describes this for your processing register.

 Changed- Messages in the dashboard and the admin module (empty queue, excluded URL, Search Console not connected, sitemap errors and others) now appear in the language of your administrator, in all six languages. Two notices of the YOOtheme Pro builder integration were in Dutch only.
- The update site is now called "Traffic Boost Indexing Updates", and it is linked to the package again after an uninstall and reinstall, so Find Updates keeps working.
- Help texts in the settings now state the default value and explain the ZOO options and where to find the Google client secret.
- The links to documentation and support point to the current product page, and the Zoo tag note links to Amazing Zoo Tag SEF.
- Under the hood: request data and plugin start-up use the current Joomla API instead of calls that Joomla 6 marks as deprecated, and the curl\_close() calls that PHP 8.5 reports as deprecated are gone.

 Fixed- One French help text in the Search Console steps was cut off.

 [ v3.1.7 04/10/2026 ](#) Added- Scheduled tasks check. The dashboard and the admin module now warn when Joomla's scheduled tasks are not running. If the list of task types cannot be built, the warning names the extension that causes the error, shows the error and lists the steps to fix it. If enabled tasks are more than six hours late, the warning lists them with their last run.

- The details and links are shown to Super Users only; other users see a short notice.

 [ v3.1.6 03/10/2026 ](#) Added- Titles and descriptions for both scheduled tasks in all six languages.

 Fixed- Scheduled tasks: with the plugin installed, no scheduled task on the site ran any more (also Joomla's own tasks such as Session GC) and System, Scheduled Tasks showed an error. The plugin now registers its two tasks the way Joomla expects.
- The plugin's own tasks "Process queue" and "Clean up log" now actually run when the scheduler starts them.
- "Clean up log" now uses the Log retention setting instead of always 30 days.
- Search Console test in shared mode no longer shows a PHP error when the Google account has no properties.

 [ v3.1.5 01/10/2026 ](#) Changed- LICENSE.txt now contains the full text of the GNU General Public License version 2, in the plugin, the dashboard module and the package. Before, it only linked to the license.

- The note about the product name and logos moved from LICENSE.txt to NOTICE.txt. NOTICE.txt is now also included in the module and the package.
- This changelog is installed with the plugin, so the release notes are available on the site itself.

 [ v3.1.4 22/09/2026 ](#) Changed- License: the package is now GPL-2.0-or-later. TERMS.txt is gone; commercial terms, updates and support are described at https://www.yoozecase.com/terms. NOTICE.txt lists what the plugin sends where, for your processing register.

- The update server address is declared once, in the package, instead of in both the package and the plugin.
- Remaining fixed texts in the dashboard (inspection results, error messages, URL counts) are now translated in all six languages.
- The help text of the YOOtheme option explains that automatic submission after a builder save only works for Super Users.

 Security- The public health report at /yc-traffic-boost-indexing-health.json is now off by default. It showed the plugin version, queue figures, sitemaps and installed integrations to anyone, and every request triggered outbound checks. It can be switched on in the Logging tab for external monitoring; without the new secret it only answers with status ok, and the full report is cached for five minutes.
- Sitemap addresses and error texts in the sitemap panel and the URL table of the dashboard are now escaped before they are shown, and the buttons no longer build JavaScript from those addresses.
- Fetching a sitemap or robots.txt stops at 50 MB, the maximum size of a sitemap.
- Sitemap discovery, the sitemap URL list and the health check now require the form token, like every other action that makes outbound requests.
- Bulk submission only accepts well-formed URLs on the site's own domain, and paging values are bounded.

 [ v3.1.3 15/07/2026 ](#) Changed- New version released.

 Fixed- Scope-guard for the Google Search Console connection. Google's consent screen shows granular permission checkboxes; when the Search Console box was left unticked the OAuth flow still "succeeded" with an e-mail-only grant: the plugin showed "connected" while every GSC call failed with an opaque 403 (ACCESS\_TOKEN\_SCOPE\_INSUFFICIENT). Now: a grant without the Search Console scope is refused at connect time (shared and own mode) with an actionable message telling the user to reconnect and tick the checkbox; a 403 scope-insufficient response at runtime wipes the useless tokens and routes into the existing amber "reconnect" state, with the same actionable help text instead of the generic "revoked" wording.

 [ v3.1.2 06/07/2026 ](#) Changed- Root fix (separate track): produce the canonical ZOO item/category URL in-

 Fixed- Canonicalisation no longer fails silently on hosts where the server cannot fetch its own URLs. When the self-fetch redirect-follow is unavailable, the plugin now logs a WARNING and shows a dashboard banner so the admin knows submissions use the raw (non-canonical) URL form on that host, instead of shipping /item/... URLs without any signal.
- Daily-submissions chart x-axis now shows NL day-month (d-m) instead of the reversed MM-DD, matching the d-m-Y dates in the log and banner.
- The "submissions per content type" table cells now carry data-label, so they collapse to labelled cards on smartphone like the log table.

 [ v3.1.1 02/07/2026 ](#) Changed- New version released.

 Fixed- Administrator dashboard module version now matches the package (was still reported as 3.0.0 in the extension manager; no functional module changes).

 [ v3.1.0 02/07/2026 ](#) Added- Google Search results block in the dashboard: clicks, impressions and top performing pages over the last 28 days, via the Search Console connection (own OAuth or shared). Cached server-side, read-only, admin-gated.

- Canonical URL submission: before submitting, the plugin resolves the site's own same-host redirects (SSRF-guarded HEAD, fail-open, cached) so internal route forms (e.g. ZOO /item/alias) are reported as their canonical SEF URL. ZOO items and categories with a dedicated menu item resolve directly to the menu path.

 Changed- Admin dashboard JavaScript moved to media/ with versioned cache busting; all strings via the Joomla text layer (Joomla.Text).
- Extension split into focused concerns (indexing AJAX, GSC AJAX); behaviour unchanged, files halved in size.
- Update feed moved to https://updates.yoozecase.com/traffic-boost-indexing/ (kebab-case public URL convention).

 Fixed- Shared Google Search Console connection silently failed to store tokens since the 3.0.0 rename (URL fragment contract); both fragment names are now accepted.
- Search engine endpoint is validated server-side against a whitelist; explicit TLS verification on IndexNow requests; key-file link escaped.
- Language file hygiene: removed corrupted leftover lines and a duplicate key; all six languages at exact key parity.

 [ v3.0.0 21/06/2026 ](#) Added- SSRF-safe fetching via a central URL guard; security gate on all write actions; central logger; dark mode and WCAG AA contrast; NL/EN/DE/FR/ES/IT.

 Changed- Renamed to Traffic Boost Indexing (plg\_system\_yc\_traffic\_boost\_indexing); database tables, CSS, JS and language keys follow the new identity. Data is migrated additively from plg\_system\_indexnow or plg\_system\_yc\_indexing.

## Schema

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "name": "Home",
            "item": "https://www.yoozecase.com"
        },
        {
            "@type": "ListItem",
            "position": 2,
            "name": "Extensions",
            "item": "https://www.yoozecase.com/extensions"
        },
        {
            "@type": "ListItem",
            "position": 3,
            "name": "Traffic Boost Indexing",
            "item": "https://www.yoozecase.com/"
        }
    ]
}
```
