For a Joomla 4 component I just cannot figure out how to successfully forward or call a Controller function from within a custom Router.

This is the Controller - it basically loads an image:


namespace Mycompany\Component\Myproject\Site\Controller;

\defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Image\Image;
use Joomla\CMS\MVC\Controller\BaseController;

class ImageController extends BaseController

    public function show()
        $id = $app->input->get('id', \null, 'string');
        // ... logic to load the image 

Using this URL the image shows up:


To support SEF URLs inside the component the URL gets called like this:


In Joomla 3 I did this by configure mod_rewrite in the .htaccess. Now this should get done by the Router:


namespace Mycompany\Component\Myproject\Site\Service;

\defined('_JEXEC') or die;

use Joomla\CMS\Component\Router\RouterBase;

class Router extends RouterBase
    public function parse(&$segments)
        $total = \count($segments);

        $vars  = [];

        switch ($segments[0]) {
            case ('img'):

                $vars['id'] = str_replace('.jpg', '', explode('_', $segments[1])[1]);
                $vars['task'] = 'image.show';
        // print_r($vars); // seems to work so far
        return $vars;
    // .... other code

The parse() function correctly splits the URL into the $vars array().

When I call the SEF image URL in the browser, I get a 404 error. - What am I missing to internally call the show() function of the ImageController class?

1 Answer 1


I wrote up in https://docs.joomla.org/J3.x:Developing_an_MVC_Component/Upgrading_to_Joomla4#Router what I found were the differences in the router between v3 and v4. In particular you have to find the Itemid in the preprocess() function and you also now have to unset the segments in parse() otherwise you'll get a 404.

  • Thanks for the tip! Unsetting the segments in the parse() did the trick. foreach ($segments as $key => $segment) {unset($segments[$key]);} I did not need a menu item to access my controller. So I did not overwrite the preprocess() in my RouterBase. Since the code was not using the router to build URLs, I simply added an build() to the RouterBase that just returns an empty segments array like this public function build(&$query) { $segments = []; return $segments;} Very valuable overview and video tutorial indeed. Thanks again!
    – cappu
    Commented Sep 1, 2023 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.