diff --git a/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.ini b/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.ini
new file mode 100644
index 00000000..e01d1dc4
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.ini
@@ -0,0 +1,24 @@
+MOD_MOKOWAAS_CATEGORIES="MokoWaaS Categories"
+MOD_MOKOWAAS_CATEGORIES_DESC="Auto-discovers article categories and renders them as a collapsible tree menu. Ideal for knowledge base and help sections."
+
+MOD_MOKOWAAS_CATEGORIES_ROOT_LABEL="Root Category"
+MOD_MOKOWAAS_CATEGORIES_ROOT_DESC="Select a parent category. Only its children (and their subcategories) will be displayed. Leave as All to show the entire category tree."
+MOD_MOKOWAAS_CATEGORIES_ALL_CATEGORIES="- All Categories -"
+
+MOD_MOKOWAAS_CATEGORIES_DEPTH_LABEL="Maximum Depth"
+MOD_MOKOWAAS_CATEGORIES_DEPTH_DESC="How many levels deep to display. 1 shows only top-level categories, 2 adds one level of subcategories, etc."
+
+MOD_MOKOWAAS_CATEGORIES_COUNT_LABEL="Show Article Count"
+MOD_MOKOWAAS_CATEGORIES_COUNT_DESC="Display the number of published articles next to each category name."
+
+MOD_MOKOWAAS_CATEGORIES_EMPTY_LABEL="Show Empty Categories"
+MOD_MOKOWAAS_CATEGORIES_EMPTY_DESC="Display categories that have no published articles. Only applies when Show Article Count is enabled."
+
+MOD_MOKOWAAS_CATEGORIES_MENUITEM_LABEL="Target Menu Item"
+MOD_MOKOWAAS_CATEGORIES_MENUITEM_DESC="The menu item to use as the base for category links. This sets the Itemid parameter for proper template and menu highlighting."
+
+MOD_MOKOWAAS_CATEGORIES_ORDER_LABEL="Category Ordering"
+MOD_MOKOWAAS_CATEGORIES_ORDER_DESC="How to sort categories within each level."
+MOD_MOKOWAAS_CATEGORIES_ORDER_TREE="Tree Order (default)"
+MOD_MOKOWAAS_CATEGORIES_ORDER_TITLE="Alphabetical"
+MOD_MOKOWAAS_CATEGORIES_ORDER_CREATED="Date Created"
diff --git a/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.sys.ini b/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.sys.ini
new file mode 100644
index 00000000..4338989f
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/language/en-GB/mod_mokowaas_categories.sys.ini
@@ -0,0 +1,2 @@
+MOD_MOKOWAAS_CATEGORIES="MokoWaaS Categories"
+MOD_MOKOWAAS_CATEGORIES_DESC="Auto-discovers article categories and renders them as a collapsible tree menu. Ideal for knowledge base and help sections."
diff --git a/src/packages/mod_mokowaas_categories/mod_mokowaas_categories.xml b/src/packages/mod_mokowaas_categories/mod_mokowaas_categories.xml
new file mode 100644
index 00000000..4fefebe0
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/mod_mokowaas_categories.xml
@@ -0,0 +1,76 @@
+
+
+ mod_mokowaas_categories
+ Moko Consulting
+ 2026-06-06
+ Copyright (C) 2026 Moko Consulting. All rights reserved.
+ GPL-3.0-or-later
+ hello@mokoconsulting.tech
+ https://mokoconsulting.tech
+ 02.34.10-dev
+ MOD_MOKOWAAS_CATEGORIES_DESC
+ Moko\Module\MokoWaaSCategories
+
+
+ services
+ src
+ tmpl
+
+
+
+ en-GB/mod_mokowaas_categories.ini
+ en-GB/mod_mokowaas_categories.sys.ini
+
+
+
+
+
+
+
+
diff --git a/src/packages/mod_mokowaas_categories/services/provider.php b/src/packages/mod_mokowaas_categories/services/provider.php
new file mode 100644
index 00000000..0ef768ba
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/services/provider.php
@@ -0,0 +1,25 @@
+registerServiceProvider(new ModuleDispatcherFactory('\\Moko\\Module\\MokoWaaSCategories'));
+ $container->registerServiceProvider(new HelperFactory('\\Moko\\Module\\MokoWaaSCategories\\Administrator\\Helper'));
+ $container->registerServiceProvider(new Module());
+ }
+};
diff --git a/src/packages/mod_mokowaas_categories/src/Dispatcher/Dispatcher.php b/src/packages/mod_mokowaas_categories/src/Dispatcher/Dispatcher.php
new file mode 100644
index 00000000..07857915
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/src/Dispatcher/Dispatcher.php
@@ -0,0 +1,32 @@
+getHelperFactory()->getHelper('CategoriesHelper');
+
+ $data['categories'] = $helper->getCategories($params);
+
+ return $data;
+ }
+}
diff --git a/src/packages/mod_mokowaas_categories/src/Helper/CategoriesHelper.php b/src/packages/mod_mokowaas_categories/src/Helper/CategoriesHelper.php
new file mode 100644
index 00000000..c33da3ec
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/src/Helper/CategoriesHelper.php
@@ -0,0 +1,148 @@
+get(DatabaseInterface::class);
+
+ $rootId = (int) $params->get('root_category', 0);
+ $maxDepth = (int) $params->get('max_depth', 3);
+ $showEmpty = (int) $params->get('show_empty', 0);
+ $showCount = (int) $params->get('show_article_count', 1);
+ $ordering = $params->get('ordering', 'lft');
+ $user = Factory::getApplication()->getIdentity();
+ $accessLevels = $user->getAuthorisedViewLevels();
+
+ // Build base query
+ $query = $db->getQuery(true)
+ ->select([
+ $db->quoteName('c.id'),
+ $db->quoteName('c.title'),
+ $db->quoteName('c.alias'),
+ $db->quoteName('c.parent_id'),
+ $db->quoteName('c.level'),
+ $db->quoteName('c.lft'),
+ $db->quoteName('c.rgt'),
+ $db->quoteName('c.description'),
+ ])
+ ->from($db->quoteName('#__categories', 'c'))
+ ->where($db->quoteName('c.extension') . ' = ' . $db->quote('com_content'))
+ ->where($db->quoteName('c.published') . ' = 1')
+ ->whereIn($db->quoteName('c.access'), $accessLevels);
+
+ // If a root category is set, constrain to its subtree
+ if ($rootId > 0)
+ {
+ $rootQuery = $db->getQuery(true)
+ ->select([$db->quoteName('lft'), $db->quoteName('rgt'), $db->quoteName('level')])
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('id') . ' = ' . $rootId);
+ $db->setQuery($rootQuery);
+ $root = $db->loadObject();
+
+ if (!$root)
+ {
+ return [];
+ }
+
+ $query->where($db->quoteName('c.lft') . ' > ' . (int) $root->lft)
+ ->where($db->quoteName('c.rgt') . ' < ' . (int) $root->rgt)
+ ->where($db->quoteName('c.level') . ' <= ' . ((int) $root->level + $maxDepth));
+ }
+ else
+ {
+ // No root — show from level 1 (skip the virtual root)
+ $query->where($db->quoteName('c.level') . ' >= 1')
+ ->where($db->quoteName('c.level') . ' <= ' . $maxDepth);
+ }
+
+ // Article count subquery
+ if ($showCount)
+ {
+ $countSub = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName('#__content', 'a'))
+ ->where($db->quoteName('a.catid') . ' = ' . $db->quoteName('c.id'))
+ ->where($db->quoteName('a.state') . ' = 1');
+ $query->select('(' . $countSub . ') AS ' . $db->quoteName('article_count'));
+ }
+
+ // Ordering
+ $validOrders = ['lft', 'title', 'created_time'];
+ $orderCol = \in_array($ordering, $validOrders, true) ? $ordering : 'lft';
+ $query->order($db->quoteName('c.' . $orderCol) . ' ASC');
+
+ $db->setQuery($query);
+ $categories = $db->loadObjectList() ?: [];
+
+ // Filter empty categories if configured
+ if (!$showEmpty && $showCount)
+ {
+ $categories = array_filter($categories, function ($cat) {
+ return (int) $cat->article_count > 0;
+ });
+ $categories = array_values($categories);
+ }
+
+ // Build nested tree
+ return $this->buildTree($categories, $rootId);
+ }
+
+ /**
+ * Build a nested tree from a flat list of categories.
+ *
+ * @param array $categories Flat list of category objects
+ * @param int $rootId Root category ID (0 for all)
+ *
+ * @return array Nested array with 'children' key on each node
+ */
+ private function buildTree(array $categories, int $rootId): array
+ {
+ $map = [];
+ $tree = [];
+
+ foreach ($categories as $cat)
+ {
+ $cat->children = [];
+ $map[$cat->id] = $cat;
+ }
+
+ foreach ($categories as $cat)
+ {
+ $parentId = (int) $cat->parent_id;
+
+ if (isset($map[$parentId]))
+ {
+ $map[$parentId]->children[] = $cat;
+ }
+ else
+ {
+ $tree[] = $cat;
+ }
+ }
+
+ return $tree;
+ }
+}
diff --git a/src/packages/mod_mokowaas_categories/tmpl/default.php b/src/packages/mod_mokowaas_categories/tmpl/default.php
new file mode 100644
index 00000000..81745845
--- /dev/null
+++ b/src/packages/mod_mokowaas_categories/tmpl/default.php
@@ -0,0 +1,138 @@
+get('show_article_count', 1));
+$menuItemId = (int) $params->get('menu_item_id', 0);
+
+if (empty($categories))
+{
+ return;
+}
+
+// Detect active category from current URL
+$app = \Joomla\CMS\Factory::getApplication();
+$activeCatId = (int) $app->input->getInt('id', 0);
+$currentView = $app->input->getCmd('view', '');
+$isCatView = \in_array($currentView, ['category', 'categories'], true);
+
+/**
+ * Build the link for a category.
+ */
+$buildLink = function (object $cat) use ($menuItemId): string {
+ $link = 'index.php?option=com_content&view=category&id=' . (int) $cat->id;
+
+ if ($menuItemId)
+ {
+ $link .= '&Itemid=' . $menuItemId;
+ }
+
+ return Route::_($link);
+};
+
+/**
+ * Check if a category or any of its descendants is the active category.
+ */
+$isActiveOrAncestor = function (object $cat) use ($activeCatId, $isCatView, &$isActiveOrAncestor): bool {
+ if (!$isCatView || !$activeCatId)
+ {
+ return false;
+ }
+
+ if ((int) $cat->id === $activeCatId)
+ {
+ return true;
+ }
+
+ foreach ($cat->children as $child)
+ {
+ if ($isActiveOrAncestor($child))
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Render a category list recursively.
+ */
+$renderTree = function (array $categories, int $depth = 1) use (
+ &$renderTree, $buildLink, $isActiveOrAncestor, $showCount, $activeCatId, $isCatView
+): void {
+ foreach ($categories as $cat):
+ $hasChildren = !empty($cat->children);
+ $isActive = $isCatView && (int) $cat->id === $activeCatId;
+ $isAncestor = $hasChildren && $isActiveOrAncestor($cat);
+ $liClass = 'item mokowaas-cat-item mokowaas-cat-level-' . $depth;
+
+ if ($isActive)
+ {
+ $liClass .= ' mm-active';
+ }
+
+ if ($hasChildren)
+ {
+ $liClass .= ' parent';
+ }
+
+ $aClass = ($hasChildren ? 'has-arrow' : 'no-dropdown');
+
+ if ($isActive)
+ {
+ $aClass .= ' mm-active';
+ }
+
+ $collapseClass = 'collapse-cat-level-' . ($depth + 1) . ' mm-collapse';
+
+ if ($isAncestor || $isActive)
+ {
+ $collapseClass .= ' mm-show';
+ }
+
+ $count = isset($cat->article_count) ? (int) $cat->article_count : 0;
+ ?>
+
+ >
+
+
+
+
+
+
+
+
+ children, $depth + 1); ?>
+
+
+
+
+
+
+
+
diff --git a/src/pkg_mokowaas.xml b/src/pkg_mokowaas.xml
index cab7478c..0608b897 100644
--- a/src/pkg_mokowaas.xml
+++ b/src/pkg_mokowaas.xml
@@ -24,6 +24,7 @@
mod_mokowaas_cpanel.zip
mod_mokowaas_cache.zip
+ mod_mokowaas_categories.zip
plg_webservices_mokowaas.zip
plg_webservices_perfectpublisher.zip
plg_task_mokowaasdemo.zip