SEO: SEF URLs, Redirects and Schema.org
|
This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production. This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages. |
Search engine optimisation in Joomla is mostly a matter of five things the core already provides and that this page walks
through in turn: readable URLs (the SEF router, URL rewriting and the System - SEF plugin), metadata (titles,
descriptions and robots directives per site, menu item and article), redirects (the Redirects component and its
404 collector), structured data (the Schema.org system plugin and its schemaorg plugin group) and crawler
files (robots.txt and sitemaps, the latter provided by extensions, not by core). It is written for site builders and
administrators, with the developer-facing internals (router rules, plugin events) marked as such.
This page was written against Joomla 6.2 (the current release; 5.4 is in its security-only period and behaves the same
for everything below unless stated), PHP 8.3 or later, and checked on 2026-10-30. The running example is the
Tallpine Hiking Club site at https://www.tallpine.example/, with trail reports under the nested categories
Trails › Alpine and Trails › Valley, a News category, English as the default language and German as the second
language. The menu structure behind these URLs is described in
Menus and Menu Items, the content model in
Articles and Categories and Tags, and the
global settings screen in Global Configuration.
How a Joomla URL is built and parsed
A Joomla page is always identified internally by query parameters: option (the component), view, layout, id,
catid, Itemid (the menu item), lang, task, tmpl and format. Without SEF URLs the address is the raw query,
for example index.php?option=com_content&view=article&id=23&catid=66&Itemid=42. The routing layer converts between
that internal form and a readable path in both directions.
-
Building starts when code calls
Route::_('index.php?option=...')(orRoute::link()to target another application such as the administrator or the API). With SEF enabled, the site router asks the component’s router to preprocess the query (which may choose the bestItemid), lets the component router build its part of the path, and prepends the path of the menu item. -
Parsing splits the incoming path on
/and matches the segments against menu item aliases from the site root downwards. The last matching menu item becomes the active menu item, which setsItemidand the component. The remaining segments are handed to that component’s router, which turns them intoview,idand so on.
Both directions are implemented by Joomla\CMS\Router\Router and, for the public site, Joomla\CMS\Router\SiteRouter.
Rules are callbacks attached with Router::attachBuildRule() and Router::attachParseRule(); a plugin may attach its
own rules, but only before parsing begins, typically in the onAfterInitialise event, which is what the language
filter plugin does for the language prefix. During parsing every path segment must be consumed: if characters remain
after all rules ran, Joomla assumes nothing recognised them and answers with a 404. Component routers are covered in
Component Site Views and Routing.
Source: Joomla Manual: Routing
Source: Joomla Manual: URLs
Tainted URLs and the single 301 (5.3 and later)
Since Joomla 4.0 an unrecognised leftover path produces a 404. Since Joomla 5.3 the router can also mark a parse as
tainted with Router::setTainted() (read it with Router::isTainted()): the URL could be resolved, but it does not
match the canonical format, for instance because the .html suffix is missing, the alias in the path is wrong while the
numeric id is right, or the URL is a duplicate form that strict routing rejects. The redirect is deliberately not issued
by the router. The System - SEF plugin checks the flag after routing and sends one 301 to a freshly built canonical
URL, so several corrections (domain, suffix, trailing slash, alias) are combined into a single hop rather than a chain.
Global Configuration: the SEO settings
The settings live in System → Global Configuration → Site, under SEO Settings (the screen links to the System - SEF plugin for further options).
| Setting | Effect | Notes |
|---|---|---|
Search Engine Friendly URLs |
Turns the SEF router on. Links become |
Required for everything below. Works with no server configuration because the path follows |
Use URL Rewriting |
Removes |
Needs the web server to send unknown paths to |
Add Suffix to URL |
Appends an extension such as |
Optional. The User Guide notes that search engines do not appear to care whether URLs end in |
Unicode Aliases |
No: aliases with non-Latin-1 characters are transliterated with the rules of the content language (for en-GB, |
Matters for the German content of the Tallpine site and for any non-Latin script. Aliases are computed when an item is saved. |
Site Name in Page Titles |
Adds the site name before or after the page title ( |
Keep titles short (about 60 characters is a common recommendation). |
Site Meta Description |
Default |
Overridden by menu item and article values. |
Robots |
Default robots meta tag: |
Setting a site-wide |
The three URL shapes the User Guide documents for the same category blog are worth knowing, since they are what you see while diagnosing a misconfigured site:
-
SEF off:
index.php?option=com_content&view=category&layout=blog&id=16&Itemid=125 -
SEF on, rewriting off:
index.php/sample-layouts/articles -
SEF on, rewriting on, suffix on:
sample-layouts/articles.html
Enable the options one at a time and test the site after each save. If a change breaks the site, the usual recovery is
to turn the last option off again from the administrator, or, if that is no longer possible, to set $sef_rewrite = false
(or $sef = false) in configuration.php, as described in
configuration.php and .htaccess.
URL rewriting on Apache
Joomla ships a htaccess.txt template in the site root. Activating it is a rename, and the Global Configuration switch
is then safe to turn on.
cd /var/www/tallpine/public_html
cp htaccess.txt .htaccess
The core SEF block of the template (since Joomla 4 it covers two applications) sends every request that is not a real file or folder to the right front controller:
RewriteEngine On
## Begin - Joomla! core SEF Section.
# PHP FastCGI fix for HTTP Authorization, required for the API application
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# SEF URLs for the API application
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_URI} !^/api/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* api/index.php [L]
# SEF URLs for the public frontend application
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]
## End - Joomla! core SEF Section.
The template also begins with request filters that reject a few classic exploit query strings (base64_encode,
script tags, GLOBALS, _REQUEST injection) with a 403, and with a commented # RewriteBase / line. Uncomment
RewriteBase only when the URL space does not map directly to the file system (a Joomla installed in a sub-folder,
for example RewriteBase /tallpine). Custom redirects such as a non-www to www rule belong in the Custom
redirects section of the template, before the core SEF block, with the [R=301,L] flags.
Common Apache errors:
-
500 Internal Server Error after the rename.
mod_rewriteis not loaded (LoadModule rewrite_module modules/mod_rewrite.sois commented out) or the template uses a directive the host forbids (Options +FollowSymlinksneedsAllowOverride Options). Fix the module or turn URL Rewriting off; the renamed file can stay. -
.htaccessignored. The virtual host needsAllowOverride All(or at leastFileInfo,Options,LimitandAuthConfig) for the Joomla directory. -
Images and assets 404 in a sub-folder install.
RewriteBaseis missing or wrong. -
Redirect loops. A custom
wwwrule duplicated by a rule in the virtual host or by a reverse proxy that terminates TLS; see Web Server, Reverse Proxy and HTTPS.
A virtual-host version that avoids .htaccess altogether (faster, because Apache does not look for override files on
every request) is the recommended form on a server you control:
<VirtualHost *:443>
ServerName www.tallpine.example
DocumentRoot /var/www/tallpine/public_html
<Directory /var/www/tallpine/public_html>
AllowOverride None
Require all granted
Options -Indexes +FollowSymlinks
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_URI} !^/api/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* api/index.php [L]
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]
</Directory>
</VirtualHost>
When the document root is the public folder of a Joomla 6 installation, the template paths are relative to that folder
and the same rules apply; the layout is described in
Installation and the public Folder.
URL rewriting on nginx
nginx has no .htaccess, and the Global Configuration screen says so ("NginX: you must configure your server"). The
rule that replaces it is try_files: serve a real file or directory if one exists, otherwise hand the request to
index.php with the original query arguments.
server {
listen 443 ssl;
server_name www.tallpine.example;
root /var/www/tallpine/public_html;
index index.php;
# Joomla! SEF URLs for the public site
location / {
try_files $uri $uri/ /index.php?$args;
}
# SEF URLs for the Web Services API application
location /api/ {
try_files $uri $uri/ /api/index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# Never serve the template files
location ~ ^/(htaccess\.txt|configuration\.php-dist|web\.config\.txt)$ {
deny all;
}
}
The User Guide documents the location / block; the /api/ location, the HTTP_AUTHORIZATION parameter (needed so
the API application sees the bearer token) and the deny block are the equivalent of the extra sections of
htaccess.txt and are an addition for sites that expose the Web Services API (see
Web Services API). The expires 1d directive that appears in one of the guide’s
examples applies to everything matched by location /, including dynamic pages; leave it out unless caching of every
response for a day is intended. After editing, run nginx -t and reload. Note that the label of the Global
Configuration option still reads "Use URL Rewriting"; on nginx it is the try_files rule that does the rewriting and
the option only tells Joomla to generate URLs without index.php.
Suffixes, trailing slashes and canonical URLs: the System - SEF plugin
The System - SEF plugin (plg_system_sef, enabled by default) does two jobs. It adds SEF support to links inside the
document HTML, which means links written by hand in article text are rewritten without any special tag, and it
enforces one canonical form of every URL by redirecting. Its options are found under System → Plugins → System - SEF.
| Option (XML name) | Behaviour |
|---|---|
Site Domain ( |
The preferred domain. |
Enforce a suffix by redirect ( |
With Add Suffix to URL enabled, URLs without the suffix redirect to the one with it. The language string states that from version 6.0 this becomes the standard behaviour and not an option any more, while the 6.2 plugin manifest still declares the field. |
Strict handling of index.php ( |
With Use URL Rewriting enabled, removes |
Trailing slash for URLs ( |
No change (default), enforce URLs without a trailing slash, or enforce URLs with one, using redirects. Applied only when Add Suffix to URL is disabled. |
Strict Routing ( |
Stops the router from accepting many URLs it considers duplicates and redirects them with a 301 to the correct one. |
Source: Joomla User Guide: SEO Basics
Recommended starting point for the Tallpine site, on a fresh site (existing sites change these with care, see the pitfalls below):
In the plugin form enter: Site Domain https://www.tallpine.example, Strict handling of
index.php on, Trailing slash set to Enforce URLs without trailing slash (no suffix), Strict Routing on.
Pitfalls to avoid:
-
Changing the suffix, trailing-slash or rewriting setting on a live site changes every URL on the site. Without matching redirects, search engines see a site-wide wave of 404s. The SEF plugin’s redirects soften this, but only for the forms it recognises.
-
Several URLs for one article. An article is reachable under its category menu item, under a Featured menu item and under a raw
index.php?...address. The recommended handling is to pick one as canonical, keep menu structure consistent, enable Strict Routing and the redirects above, and keep the old forms out of internal links. -
Domain redirect loops. A Site Domain that differs from the one a reverse proxy forwards produces an endless redirect; configure forwarded headers first (see Web Server, Reverse Proxy and HTTPS).
-
Letting a CDN or full-page cache ignore the redirect rules. Cache purging after changing SEF options is covered in Caching and Performance.
Menu-based URL design
In Joomla the URL path is mostly the menu. Each menu item has an Alias (lower-case, hyphenated, unique among its siblings) and a parent; the item’s path is the chain of aliases from the root. A component’s own segments are added after the path of the menu item. For the Tallpine site:
| Menu structure | Link target | Resulting URL |
|---|---|---|
Trails (alias |
Category Blog, |
|
An article Lake Tarn Circuit, alias |
Article |
|
News (alias |
Category List |
|
An article with no suitable menu item |
Article, Itemid falls back to the best match (home) |
|
Design rules that follow from this:
-
Create a menu item for every important category and give it a good alias. An article whose category has no menu item gets a long
/component/content/...address or lands on the home item. This is the main structural SEO fix on most Joomla sites. -
Keep the menu tree shallow. Each nesting level adds a segment; two or three levels are enough.
-
Hidden menu items are legitimate. A menu item in a menu that is not shown by any module still defines a URL path; use one (a Hidden menu) to give a section a clean address without exposing it in the navigation.
-
Aliases are history. Renaming an alias changes the URL of every item below it. Add a redirect when you rename.
-
Multilingual sites add a language code prefix (
/en/,/de/) through the language filter and keep one menu tree per language, linked by associations; the interaction with SEF is covered in Multilingual Sites. -
IDs in URLs. Joomla 4 and later use the modern router for core components, which can resolve articles by alias alone. In Articles → Options → Integration, the Remove IDs from URLs setting (
sef_ids, default No) drops the numeric id from article and category URLs (/news/trail-reopenedinstead of/news/12-trail-reopened). Turn it on only when aliases are unique within a category, and before the site goes live, because it changes every URL.
Where a custom component such as com_tallpine needs its own URL segments (/trails/alpine/lake-tarn-circuit), it
implements a RouterView with MenuRules, StandardRules and NomenuRules; see
Component Site Views and Routing.
Source: Joomla User Guide: SEO Basics
Metadata per site, menu item and article
Every page needs a distinct <title> and <meta name="description">. Joomla resolves them from the most specific
source available.
| Where | Fields | Becomes |
|---|---|---|
Menu item, tab Page Display |
Browser Page Title |
The |
Menu item, tab Metadata |
Meta Description, Keywords, Robots |
|
Article, tab Publishing |
Meta Description, Keywords, Robots, Author, Content Rights |
|
Global Configuration, Site |
Site Meta Description, Robots |
Defaults for pages with none of the above |
Notes for writing good metadata:
-
Title. About 60 characters, unique per page, with the topic first. The article title is rendered as the page
H1, so the article body should start atH2and nestH3toH5below it; do not use headings for visual styling. -
Description. A readable sentence of roughly 155 characters. Search engines sometimes show it under the title, but may substitute their own text.
-
Keywords. The meta keywords field still exists. Search engines no longer use it for ranking; older books and tutorials that recommend keyword density or keyword stuffing give outdated advice, and the field can stay empty. Tags (see Categories and Tags) are a navigation feature, not a ranking one.
-
Robots. Per page
noindexis the right tool for thank-you pages, internal search results and members-only summaries; it does not protect content (see the security note onrobots.txtbelow). -
Images. Use descriptive file names with hyphens, meaningful alt text, and modern formats; responsive images and WebP generation come from the Media Manager and extensions (see Media Manager).
-
Canonical tags. Core does not output a
rel="canonical"link for articles; a canonical URL in the head comes from the SEF redirects described above or from a third-party extension. The User Guide describes one such plugin and notes that it needs a code change for Joomla 4 and 5, so check any extension against your version before relying on it.
A template can set head data from code, which is occasionally useful for an extension view:
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
// In a component HtmlView::display(), after loading the trail item
$document = Factory::getApplication()->getDocument();
$document->setTitle($this->item->title . ' - Tallpine Hiking Club');
$document->setDescription(
'Distance ' . $this->item->distance_km . ' km, elevation gain '
. $this->item->elevation_gain . ' m, difficulty ' . $this->item->difficulty . '.'
);
if ($this->item->noindex) {
$document->setMetaData('robots', 'noindex, follow');
}
Source: Joomla Manual: URLs
Source: Joomla User Guide: SEO Basics
Redirects: the Redirects component and 404 collection
Moving or renaming content leaves old addresses behind. The Redirects component (Components → Redirects,
com_redirect) holds a table of Expired URL → New URL pairs; the System - Redirect plugin
(plg_system_redirect) applies it. Both must be enabled; the component shows a message when the plugin is disabled.
How it works internally
-
The plugin listens for the
onErrorevent on the site application and acts only on errors with HTTP code 404 (the administrator is ignored). -
It looks up the requested URL (with and without domain, and with the query string) in the redirect table.
-
If a published entry exists with a new URL, the visitor receives a redirect. By default the status is 301. Only if Advanced mode is enabled in the component options (
mode) is the per-entry Redirect Status Code used; a code outside 300-399 is raised as the error status instead (for example a 410 Gone). -
If no entry exists and Collect URLs is enabled on the plugin (
collect_urls, enabled by default), the 404 URL is stored as a new, unpublished entry so the administrator can later give it a destination. Repeated 404s increment the Hits counter of an existing entry. -
If an entry exists but is unpublished, the hit counter is incremented and the visitor still gets the 404 page.
Plugin options: Collect URLs, Include Domain Name in Expired URL (store absolute or relative expired URLs) and
Exclude URLs (terms or regular expressions never stored, for example /wp-login.php or xmlrpc).
Working with the list
-
Open the list: the Link Referrer column tells you who linked to the broken URL, so you can fix the source.
-
Edit an entry, set New URL (a relative path such as
/trails/alpine/lake-tarn-circuitis fine), choose the status code if Advanced mode is on, add a Comment and publish it. -
Batch: the Bulk process to add new URLs toolbar action accepts one
old-url|new-urlpair per line (the separator is configurable in the options), and Batch Update Selected URL(s) assigns one new URL to many entries. -
Use 301 for permanent moves (the default and the usual choice), 302/307 only for temporary ones, and 410 for content that is gone for good.
An import for the Tallpine site after a migration from an old site, using the batch field:
/index.php/weg-zum-see|/de/wanderwege/talweg/weg-zum-see
/trails/valley/old-lake-path|/trails/valley/lake-path
/news/2024/spring-opening|/news/spring-opening
Source: Joomla Manual: Routing
Pitfalls
-
The table grows without bound. Bots probing for
/wp-login.phpfill it with junk. Add Exclude URLs patterns, or disable Collect URLs once the list has been triaged, and purge unused entries. -
Redirect chains. A redirect target must be a final URL. Redirecting to a URL that the SEF plugin then redirects again costs a second hop.
-
Server-level redirects are faster. A permanent move of a whole directory is better done in the web server (
RewriteRule, nginxreturn 301) than by thousands of database rows. -
Redirects only fire for 404s. An old URL that still resolves to something (for example a menu alias that now points to different content) is never redirected; fix it by changing the alias or by adding a server rule.
-
A custom 404 page is an error page of the template (
error.php); see Error, Offline and Component Pages.
A server-level equivalent for a whole renamed section:
# Old German paths moved under /de/wanderwege
location ~ ^/touren/(.*)$ {
return 301 /de/wanderwege/$1;
}
Schema.org structured data
Structured data is a standard way for a page to tell search engines what it is about: not just text, but "this is an event, with this start date and place". Joomla produces it as JSON-LD in the page head. The feature arrived in Joomla 5.0 (it grew out of a Google Summer of Code 2022 project) and is unchanged in concept in 6.2. It is split into one system plugin and a group of type plugins.
The plugins
-
System - Schema.org (
plg_system_schemaorg) holds the site-wide data, adds a Schema tab to supported edit forms, stores the values in the#__schemaorgtable and prints the JSON-LD into the head. -
Plugins in the
schemaorggroup each define one type and its form: Article (5.1), BlogPosting, Book, Event, JobPosting, Organization, Person, Recipe and Custom. Only enabled plugins appear in the Schema Type list. Third-party types exist in the Joomla Extensions Directory (category Schema Plugins).
All of them are in System → Plugins; filter by the group schemaorg or by the word schema.
Configuring the site-wide data
Open System - Schema.org and fill in:
| Field (XML name) | Meaning |
|---|---|
Base Type ( |
Organization (clubs, companies, NGOs) or Person (a personal site). Default Organization. For Tallpine: Organization. |
Name ( |
Shown for an organization, or for a person who is not an existing user. When empty the site name from Global Configuration is used. For a person you can instead select a Joomla user ( |
Image ( |
The logo, taken from the Media Manager. Joomla makes the URL absolute and records width and height. |
Social Media Accounts ( |
A repeatable list of profile URLs, output as |
The page stays empty of structured data until the plugin has been saved with a base type at least once: the editor
shows "you have to configure the plugin first". Then enable the type plugins you need (for the Tallpine site: Article
for trail reports, Event for the guided hikes, Organization) and, optionally, restrict each plugin to the extensions
it may serve with its Allowed list (allowedlist, a list of components that implement the schema.org service
interface).
Adding data to an article
In Content → Articles, edit an article and open the Schema tab. Pick the Schema Type that matches it. The form then shows the type’s fields; the Event type, for instance, offers the featured image, event name, summary or description, start date, location details (name and postal address), organizer details (a person or an organization, with name, URL, logo, e-mail and address) and an optional aggregate rating. A Generic Field section lets you add a custom property that the default form lacks. The data is saved with the article and removed when the article is deleted.
Article fields can also be fed to schema data; the User Guide page on accessing Schema.org custom fields explains the
tab. Custom fields in general are covered in Custom Fields. Today only the
com_content.article context is registered by core; no other core component exposes a Schema tab, and a custom
component can opt in by implementing SchemaorgServiceInterface (with SchemaorgServiceTrait) and returning its
contexts from getSchemaorgContexts(). A form field, schemaorgcomponentsections, lists the enabled components that
implement the interface, and is what the type plugins use for their Allowed option.
Source: Joomla User Guide: Event type
What is generated
On a public article page whose context is supported, the system plugin builds a single JSON-LD document with an
@graph, in the onBeforeCompileHead event:
-
the base entity (Organization or Person), with name,
url, logo andsameAslinks; -
a WebSite node, with a
SearchActionthat points at the Smart Search page when a Smart Search module (mod_finder) is published (see Smart Search); -
a WebPage node with the current URL, title, meta description, language and a link to breadcrumbs when the breadcrumbs module produced schema data;
-
the item node from the Schema tab, for example an
Event, linked to the page withisPartOf.
Empty values are removed, and the output is escaped with JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS and
JSON_HEX_QUOT so editor-supplied text cannot close the script element. It is delivered as an inline script of type
application/ld+json, pretty-printed only when debug mode is on. For a guided hike on the Tallpine site the result
looks like this (shortened):
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://www.tallpine.example/#/schema/Organization/base",
"name": "Tallpine Hiking Club",
"url": "https://www.tallpine.example/",
"sameAs": ["https://social.example/tallpine"]
},
{
"@type": "WebSite",
"@id": "https://www.tallpine.example/#/schema/WebSite/base",
"url": "https://www.tallpine.example/",
"name": "Tallpine Hiking Club",
"publisher": {"@id": "https://www.tallpine.example/#/schema/Organization/base"}
},
{
"@type": "Event",
"@id": "https://www.tallpine.example/#/schema/com_content/article/23",
"name": "Lake Tarn guided circuit",
"startDate": "2026-11-14T08:30",
"location": {
"@type": "Place",
"name": "Lake Tarn car park",
"address": {"@type": "PostalAddress", "addressLocality": "Tarn Valley"}
}
}
]
}
Extending the output (developers)
The system plugin dispatches three events to plugins of the schemaorg group, each wrapped in an event class:
onSchemaPrepareData and onSchemaPrepareForm (while an article form is prepared), onSchemaPrepareSave (after an
article is saved) and onSchemaBeforeCompileHead (just before the JSON-LD is printed, with the whole graph in
getSchema()). The core types are final classes that extend CMSPlugin, implement SubscriberInterface and use
SchemaorgPluginTrait plus helper traits for dates and images; a new type is a normal plugin of the schemaorg group
with a forms/schemaorg.xml and the same structure. A smaller job, adjusting the output of other types, only needs the
last event:
<?php
namespace Tallpine\Plugin\Schemaorg\Tallpine\Extension;
use Joomla\CMS\Event\Plugin\System\Schemaorg\BeforeCompileHeadEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
\defined('_JEXEC') or die;
final class Tallpine extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['onSchemaBeforeCompileHead' => 'onSchemaBeforeCompileHead'];
}
public function onSchemaBeforeCompileHead(BeforeCompileHeadEvent $event): void
{
$schema = $event->getSchema();
$graph = $schema->get('@graph');
foreach ($graph as $key => $node) {
if (($node['@type'] ?? '') === 'Event') {
// Every guided hike is free for members and public
$graph[$key]['isAccessibleForFree'] = true;
}
}
$schema->set('@graph', $graph);
}
}
The plugin needs the usual services/provider.php and manifest with group="schemaorg"; see
Plugin Development and Events.
Checking and pitfalls
-
Test a published page with the search engines' rich-result tester or the Schema Markup Validator and look for errors in required properties (for events: name, start date and location).
-
No visible output. The plugin is not configured (save it once), the relevant type plugin is disabled, the article has no Schema Type selected, or the page is not an article view (category blogs and lists carry only the base graph, if anything).
-
Duplicated markup. Templates, SEO extensions or older "rich snippet" plugins may also emit JSON-LD. Keep one source of truth.
-
Markup must match what the visitor sees. Do not mark up content that is hidden or does not exist; search engines may penalise it.
-
Older material. Joomla! 4 Masterclass (2023) covers Joomla 4 and has nothing about Schema.org; third-party schema extensions written for Joomla 4 may not support the Joomla 5 and 6 event classes, so check their compatibility before upgrading. Developing Extensions for Joomla! 5 (2023) does not describe the
schemaorgplugin group.
Sitemaps
Joomla core does not generate an XML sitemap. The User Guide recommends one for dynamic sites because manually maintained sitemaps do not scale, and points to extensions from the Joomla Extensions Directory (category Sitemap), which can produce both HTML sitemaps for visitors and XML sitemaps for crawlers. Such extensions typically let you choose which menu items to include, set the change frequency and priority, and optionally expand a menu item into its articles or items (for example every article of a category blog).
Practical advice:
-
Include only canonical, indexable URLs; leave out
noindexpages and redirected URLs. -
In the multilingual Tallpine site, include both languages and the
hreflangalternates that the language associations provide. -
Reference the sitemap from
robots.txt(Sitemap:line) and submit it in each search engine’s webmaster tool. -
Submitting a sitemap does not guarantee indexing; it only gives crawlers hints.
-
The Smart Search index (Smart Search) is a separate, internal search feature and is not a sitemap.
robots.txt
Joomla ships a robots.txt.dist file. Copy it to robots.txt in the web root (the document root, not a sub-folder)
and adjust it. The distributed file blocks the application folders from crawling:
User-agent: *
Disallow: /administrator/
Disallow: /api/
Disallow: /bin/
Disallow: /cache/
Disallow: /cli/
Disallow: /components/
Disallow: /includes/
Disallow: /installation/
Disallow: /language/
Disallow: /layouts/
Disallow: /libraries/
Disallow: /logs/
Disallow: /modules/
Disallow: /plugins/
Disallow: /tmp/
A Tallpine production file adds the sitemap and keeps the media and template assets crawlable, since search engines need CSS and JavaScript to render pages:
User-agent: *
Disallow: /administrator/
Disallow: /api/
Disallow: /cache/
Disallow: /cli/
Disallow: /logs/
Disallow: /tmp/
Disallow: /component/content/
Allow: /media/
Allow: /templates/
Sitemap: https://www.tallpine.example/sitemap.xml
Things to know:
-
Blocked folders and rendering. Core front-end assets are published under
/media/and template assets under/templates/, which the distributed file leaves open. If a third-party extension loads CSS or JavaScript from a blocked folder, crawlers cannot render the page; check the search engine’s URL inspection result and addAllowrules as needed. -
robots.txtis not an access control. It is public and advisory. Do not list secret paths in it, and protect private content with access levels (see Users, Groups, Access Levels and ACL), not withDisallow. -
Disallowis notnoindex. A blocked URL can still appear in results if it is linked from elsewhere; use the Robots metadata (noindex) on a page that crawlers are allowed to read. -
A sub-folder install needs
robots.txtat the site root of the domain with the folder name prefixed to every path (Disallow: /joomla/administrator/). -
Staging sites should be blocked by authentication, not only by
Disallow: /.
Checklist for the Tallpine launch
| Step | Check |
|---|---|
1 |
Search Engine Friendly URLs and Use URL Rewriting on; |
2 |
A menu item exists for every important category; aliases are final; Remove IDs from URLs decided before launch. |
3 |
System - SEF: Site Domain set, Strict handling of index.php on, trailing slash policy chosen, Strict Routing on. |
4 |
Site Meta Description set; Robots at |
5 |
Redirects: System - Redirect enabled, Collect URLs on during the first weeks, old addresses imported, junk excluded. |
6 |
Schema.org: System plugin saved with Organization data and logo; Article and Event plugins enabled; a sample page validates. |
7 |
|
8 |
After go-live: monitor the search console for 404s and coverage, and review the Redirects list weekly. |