diff --git a/src/packages/plg_system_mokoog/language/en-GB/plg_system_mokoog.ini b/src/packages/plg_system_mokoog/language/en-GB/plg_system_mokoog.ini
index 9e75b06..987bbc1 100644
--- a/src/packages/plg_system_mokoog/language/en-GB/plg_system_mokoog.ini
+++ b/src/packages/plg_system_mokoog/language/en-GB/plg_system_mokoog.ini
@@ -27,3 +27,7 @@ PLG_SYSTEM_MOKOOG_FIELD_TELEGRAM_CHANNEL="Telegram Channel"
PLG_SYSTEM_MOKOOG_FIELD_TELEGRAM_CHANNEL_DESC="Your Telegram channel handle (e.g. @mokoconsulting). Outputs a telegram:channel meta tag for Telegram link previews."
PLG_SYSTEM_MOKOOG_FIELD_AUTO_RESIZE="Auto-resize Images"
PLG_SYSTEM_MOKOOG_FIELD_AUTO_RESIZE_DESC="Automatically resize images to 1200x630px (Facebook recommended) using center crop. Generated images are saved to images/mokoog/generated/."
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_ENABLED="Enable JSON-LD"
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_ENABLED_DESC="Output JSON-LD structured data (Article, WebPage) for Google rich results."
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_BREADCRUMBS="JSON-LD Breadcrumbs"
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_BREADCRUMBS_DESC="Output BreadcrumbList JSON-LD schema from Joomla's pathway."
diff --git a/src/packages/plg_system_mokoog/language/en-US/plg_system_mokoog.ini b/src/packages/plg_system_mokoog/language/en-US/plg_system_mokoog.ini
index 9e75b06..987bbc1 100644
--- a/src/packages/plg_system_mokoog/language/en-US/plg_system_mokoog.ini
+++ b/src/packages/plg_system_mokoog/language/en-US/plg_system_mokoog.ini
@@ -27,3 +27,7 @@ PLG_SYSTEM_MOKOOG_FIELD_TELEGRAM_CHANNEL="Telegram Channel"
PLG_SYSTEM_MOKOOG_FIELD_TELEGRAM_CHANNEL_DESC="Your Telegram channel handle (e.g. @mokoconsulting). Outputs a telegram:channel meta tag for Telegram link previews."
PLG_SYSTEM_MOKOOG_FIELD_AUTO_RESIZE="Auto-resize Images"
PLG_SYSTEM_MOKOOG_FIELD_AUTO_RESIZE_DESC="Automatically resize images to 1200x630px (Facebook recommended) using center crop. Generated images are saved to images/mokoog/generated/."
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_ENABLED="Enable JSON-LD"
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_ENABLED_DESC="Output JSON-LD structured data (Article, WebPage) for Google rich results."
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_BREADCRUMBS="JSON-LD Breadcrumbs"
+PLG_SYSTEM_MOKOOG_FIELD_JSONLD_BREADCRUMBS_DESC="Output BreadcrumbList JSON-LD schema from Joomla's pathway."
diff --git a/src/packages/plg_system_mokoog/mokoog.xml b/src/packages/plg_system_mokoog/mokoog.xml
index 4df713b..c1a12ac 100644
--- a/src/packages/plg_system_mokoog/mokoog.xml
+++ b/src/packages/plg_system_mokoog/mokoog.xml
@@ -126,6 +126,28 @@
+
+
+
+
+
+
+
+
diff --git a/src/packages/plg_system_mokoog/src/Extension/MokoOG.php b/src/packages/plg_system_mokoog/src/Extension/MokoOG.php
index 38a5644..dbce0ab 100644
--- a/src/packages/plg_system_mokoog/src/Extension/MokoOG.php
+++ b/src/packages/plg_system_mokoog/src/Extension/MokoOG.php
@@ -18,6 +18,7 @@ use Joomla\CMS\Uri\Uri;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
use Joomla\Plugin\System\MokoOG\Helper\ImageHelper;
+use Joomla\Plugin\System\MokoOG\Helper\JsonLdBuilder;
final class MokoOG extends CMSPlugin implements SubscriberInterface
{
@@ -134,6 +135,29 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
if ($telegramChannel) {
$doc->setMetaData('telegram:channel', $telegramChannel);
}
+
+ // JSON-LD structured data
+ if ($this->params->get('jsonld_enabled', 1)) {
+ $imageUrl = $image ? $this->resolveImageUrl($image) : '';
+
+ if ($option === 'com_content' && $view === 'article' && $id > 0) {
+ $schema = JsonLdBuilder::buildArticle($id, $title, $description, $imageUrl);
+ } else {
+ $schema = JsonLdBuilder::buildWebPage($title, $description);
+ }
+
+ if ($schema) {
+ $doc->addCustomTag(JsonLdBuilder::toScriptTag($schema));
+ }
+
+ if ($this->params->get('jsonld_breadcrumbs', 1)) {
+ $breadcrumbs = JsonLdBuilder::buildBreadcrumbs();
+
+ if ($breadcrumbs) {
+ $doc->addCustomTag(JsonLdBuilder::toScriptTag($breadcrumbs));
+ }
+ }
+ }
}
/**
diff --git a/src/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php b/src/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php
new file mode 100644
index 0000000..d56f029
--- /dev/null
+++ b/src/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php
@@ -0,0 +1,168 @@
+
+ * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
+ * @license GNU General Public License version 3 or later; see LICENSE
+ */
+
+namespace Joomla\Plugin\System\MokoOG\Helper;
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Factory;
+use Joomla\CMS\Uri\Uri;
+
+class JsonLdBuilder
+{
+ /**
+ * Build Article schema for a com_content article.
+ *
+ * @param int $articleId Article ID
+ * @param string $title Page title
+ * @param string $description Page description
+ * @param string $image Image URL (absolute)
+ *
+ * @return array|null
+ */
+ public static function buildArticle(int $articleId, string $title, string $description, string $image): ?array
+ {
+ if ($articleId <= 0) {
+ return null;
+ }
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName([
+ 'a.created', 'a.modified', 'a.publish_up',
+ 'u.name',
+ ]))
+ ->from($db->quoteName('#__content', 'a'))
+ ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by'))
+ ->where($db->quoteName('a.id') . ' = ' . $articleId);
+
+ $db->setQuery($query);
+ $article = $db->loadObject();
+
+ if (!$article) {
+ return null;
+ }
+
+ $schema = [
+ '@context' => 'https://schema.org',
+ '@type' => 'Article',
+ 'headline' => $title,
+ 'description' => $description,
+ 'url' => Uri::getInstance()->toString(),
+ 'datePublished' => $article->publish_up ?: $article->created,
+ 'dateModified' => $article->modified ?: $article->created,
+ ];
+
+ if (!empty($article->name)) {
+ $schema['author'] = [
+ '@type' => 'Person',
+ 'name' => $article->name,
+ ];
+ }
+
+ if (!empty($image)) {
+ $schema['image'] = $image;
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Build WebPage schema for non-article pages.
+ *
+ * @param string $title Page title
+ * @param string $description Page description
+ *
+ * @return array
+ */
+ public static function buildWebPage(string $title, string $description): array
+ {
+ return [
+ '@context' => 'https://schema.org',
+ '@type' => 'WebPage',
+ 'name' => $title,
+ 'description' => $description,
+ 'url' => Uri::getInstance()->toString(),
+ ];
+ }
+
+ /**
+ * Build BreadcrumbList schema from Joomla's pathway.
+ *
+ * @return array|null
+ */
+ public static function buildBreadcrumbs(): ?array
+ {
+ $app = Factory::getApplication();
+ $pathway = $app->getPathway();
+ $items = $pathway->getPathway();
+
+ if (empty($items)) {
+ return null;
+ }
+
+ $listItems = [];
+ $position = 1;
+
+ foreach ($items as $item) {
+ $url = $item->link;
+
+ if ($url && !str_starts_with($url, 'http')) {
+ $url = rtrim(Uri::root(), '/') . '/' . ltrim($url, '/');
+ }
+
+ $listItems[] = [
+ '@type' => 'ListItem',
+ 'position' => $position,
+ 'name' => $item->name,
+ 'item' => $url ?: Uri::getInstance()->toString(),
+ ];
+
+ $position++;
+ }
+
+ return [
+ '@context' => 'https://schema.org',
+ '@type' => 'BreadcrumbList',
+ 'itemListElement' => $listItems,
+ ];
+ }
+
+ /**
+ * Build Organization schema from site configuration.
+ *
+ * @param string $siteName Site name
+ *
+ * @return array
+ */
+ public static function buildOrganization(string $siteName): array
+ {
+ return [
+ '@context' => 'https://schema.org',
+ '@type' => 'Organization',
+ 'name' => $siteName,
+ 'url' => Uri::root(),
+ ];
+ }
+
+ /**
+ * Encode a schema array to a JSON-LD script tag string.
+ *
+ * @param array $schema Schema data
+ *
+ * @return string
+ */
+ public static function toScriptTag(array $schema): string
+ {
+ $json = json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+
+ return '';
+ }
+}