Consider the following Craft 3 example module structure:


...and consider the following action in ExampleController.php:

public function actionExampleRoute()
    die('in example-route action');

Assuming everything is set up and configured correctly, is this the correct URL for triggering the action in ExampleController.php?


If so, this is not working for me; it instead results in a 404/Template not found error. I'm not sure if I misunderstand how to determine the correct URL or if I set up something incorrectly.

I am working with the example provided here and I believe I have followed the instructions correctly. The instructions include a simple test of the module, and the test succeeds--I'm just not able to get the controller action to work as expected.


It depends on the module handle you chose in config/app.php (the module handle is the key affected to your module in the modules array).

If you want the URL to be /business-logic/example/example-route, the module key must be business-logic:

use modules\businesslogic\BusinessLogic;

return [
    'modules' => [
        'business-logic' => BusinessLogic::class,
    'bootstrap' => [

The cause of the issue turned out to be an incorrect controllerNamespace reference in the Business Logic Template example. If anyone else is using the example project to jumpstart a Craft module, be aware of the following:

In that example, the ExampleController.php controller specifies this namespace...

namespace modules\businesslogic\controllers;

...but BusinessLogic.php sets the controllerNameSpace like this, (note that it's missing modules\)...

// Adjust controller namespace for console requests
if (Craft::$app->getRequest()->getIsConsoleRequest()) {
    $this->controllerNamespace = 'businesslogic\\console\\controllers';

...it worked for me after I changed it to match the ExampleController namespace, (note the addition of modules\ as well as handling both console and web requests)...

// Adjust controller namespace for console requests
if (Craft::$app->getRequest()->getIsConsoleRequest()) {
    $this->controllerNamespace = 'modules\\businesslogic\\console\\controllers';
} else {
    $this->controllerNamespace = 'modules\\businesslogic\\controllers';


To answer my own original question, the correct way to call the module action is:


Note that /actions precedes the module/controller/action portion of the URL, and that the controller and action are kebab-cased. As @nstCactus points out, the module is the module handle defined in config/app.php.


Whenever I'm using controllers for direct URL access (e.g. from JavaScript via an XHR or fetch request), I've accessed them using a query string rather than relying on rewritten URLs. So, like this:


If the controller method is intended to be triggered from a form submit, you can just use the actionInput Twig function:

<form method="post">
   {{ actionInput('business-logic/example/example-route') }}

   {# Some form input fields here... #}

    <button type="submit">

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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