<p>Unlike plugins, modules have to register their own <a href="https://craftcms.com/docs/4.x/extend/template-roots.html" rel="nofollow noreferrer">template root</a>.</p>
<p>Stick something like this in your module's <code>init()</code> method:</p>
<pre class="lang-php prettyprint-override"><code>use yii\base\Event;
use craft\events\RegisterTemplateRootsEvent;
use craft\web\View;

...

Event::on(
    View::class,
    View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
    function (RegisterTemplateRootsEvent $e) {
        if (is_dir($baseDir = $this-&gt;getBasePath() . DIRECTORY_SEPARATOR . 'templates')) {
            $e-&gt;roots[$this-&gt;id] = $baseDir;
        }
    }
);
</code></pre>
<p>This will register a template root <code>'check-date'</code> (i.e. the ID for your module, as per the config in your <code>config/app.php</code> file), that points to the <code>modules/checkdate/src/templates</code> folder.</p>
<p>This means that you'll also have to correct your email template path from</p>
<pre class="lang-php prettyprint-override"><code>'checkdate/emails/_email.twig'
</code></pre>
<p>to</p>
<pre class="lang-php prettyprint-override"><code>'check-date/emails/_email.twig'
</code></pre>
<p>It's also, generally, a good idea to be explicit about the view mode when rendering custom templates. In this case, you'll want Craft to use the 'CP' view mode, so the full <code>View::render()</code> method call should look like this:</p>
<pre class="lang-php prettyprint-override"><code>use craft\web\View;

...

$htmlBody = Craft::$app-&gt;view-&gt;renderTemplate(
    'check-date/emails/_email.twig',
    [
        'name' =&gt; $title
    ],
    View::TEMPLATE_MODE_CP
);
</code></pre>

CC BY-SA 4.0
2024-03-21T16:20:04.900Z
Couldn't find that myself! Thanks for clearing that out, works as expected now.
Arno Ramon
CC BY-SA 4.0
2024-03-21T16:10:14.173Z
2024-03-21T16:10:14.173Z
Mats Mikkel Rummelhoff
<p>I believe you'll need to switch the <code>templateMode</code> to be <code>TEMPLATE_MODE_SITE</code> in your call to <code>renderTemplate()</code> -- otherwise it'll default to attempting to load templates from the main Craft app.</p>
<pre class="lang-php prettyprint-override"><code>$htmlBody = Craft::$app-&gt;view-&gt;renderTemplate(
    'checkdate/emails/_email.twig',
    ['name' =&gt; $title],
    \craft\web\View::TEMPLATE_MODE_SITE
);
</code></pre>
<p><a href="https://docs.craftcms.com/api/v4/craft-web-controller.html#method-rendertemplate" rel="nofollow noreferrer">https://docs.craftcms.com/api/v4/craft-web-controller.html#method-rendertemplate</a></p>

CC BY-SA 4.0
2024-03-21T16:07:30.540Z
Hi James,

I'm a step closer, but now it will look in Craft's main template folder. Is there a way to keep everything inside the module folder?

So with you're snippet it looks for /templates/checkdate/emails/_email.twig -> works well tho.

But can i make it so it uses /modules/checkdate/src/templates/checkdate/emails/_email.twig instead?
Arno Ramon
CC BY-SA 4.0
2024-03-21T16:13:15.323Z
This answer assumes that the `email.twig` template is in the _site_ template folder (i.e. `templates/checkdate/emails/_email.twig`). I believe OP wants to render the template from a folder inside their module's folder, in which case `TEMPLATE_MODE_SITE` is wrong.
Mats Mikkel Rummelhoff
CC BY-SA 4.0
2024-03-21T17:08:34.790Z
Ah right I hadn't considered that approach. Interesting. Personally if it's a _module_ I'd prefer to keep all my templates together (especially since emails would likely `extend` some shared email layout templates. Only reason I'd consider bundling it together would be if it's a _plugin_ to be distributed.
James Smith
CC BY-SA 4.0
2024-03-21T18:28:53.680Z
@JamesSmith That's completely valid IMO. I've done both; the approach typically depends on how tightly coupled the module is to the actual site/build. For modules that are mostly self-contained, I think it's nice to keep everything pertaining to that module, including templates, in the module source folder.
Mats Mikkel Rummelhoff
CC BY-SA 4.0
2024-03-21T16:00:11.760Z
2024-03-21T16:00:11.760Z
James Smith
<p>I'm creating my first module. Inside this module, I check a few entries for some conditions, and if all conditions are met, I want to send out a personal email.</p>
<p>Everything worked, but I wanted to clean up my code and put the email body inside a template file so it's more organized. This is where I'm having trouble.</p>
<p>Whenever I run the command ./craft check-date/send-mail, I keep getting the following error:</p>
<pre><code>Unable to find the template “checkdate/emails/_email.twig”.
</code></pre>
<p>Here is a striped down version of my module:</p>
<p><strong>modules/checkdate/src/CheckDate.php</strong></p>
<pre class="lang-php prettyprint-override"><code>&lt;?php

namespace modules\checkdate;

use Craft;
use yii\base\Module as BaseModule;

/**
 * CheckDate module
 *
 * @method static CheckDate getInstance()
 */
class CheckDate extends BaseModule
{
    public function init(): void
    {
        Craft::setAlias('@modules/check-date', __DIR__);

        // Set the controllerNamespace based on whether this is a console or web request
        if (Craft::$app-&gt;request-&gt;isConsoleRequest) {
            $this-&gt;controllerNamespace = 'modules\\checkdate\\console\\controllers';
        } else {
            $this-&gt;controllerNamespace = 'modules\\checkdate\\controllers';
        }

        parent::init();
    }
}
</code></pre>
<p><strong>modules/checkdate/src/console/controllers/SendMailController.php</strong></p>
<pre class="lang-php prettyprint-override"><code>&lt;?php

namespace modules\checkdate\console\controllers;

use Craft;
use craft\console\Controller;
use craft\elements\Entry;

use craft\helpers\Html;
use craft\helpers\Template;
use craft\mail\Message;

use yii\base\InvalidConfigException;

/**
 * Greet Controller
 */
class SendMailController extends Controller
{
    /**
     * Issues a greeting to new Craft developers.
     */
    public function actionIndex()
    {

        // Get all entries in your section
        $entries = Entry::find()
            -&gt;section('dates')
            -&gt;fieldSendedMail(false)
            -&gt;all();

        foreach ($entries as $entry) {

            // Variables
            $title = $entry-&gt;title;

            // Send email with HTML body
            try {
                // Render the HTML template with variables
                $htmlBody = Craft::$app-&gt;view-&gt;renderTemplate(
                    'checkdate/emails/_email.twig',
                    [
                        'name' =&gt; $title
                    ]
                );
                $mailer = Craft::$app-&gt;getMailer();
                $message = new Message();
                $message-&gt;setTo('recipient@example.com');
                $message-&gt;setSubject('Subject of the Email');
                $message-&gt;setHtmlBody($htmlBody);
                $mailer-&gt;send($message);
            } catch (InvalidConfigException $e) {
                // Handle any exceptions that occur during email sending
                Craft::error('Error sending email for entry ' . $entry-&gt;id . ': ' . $e-&gt;getMessage(), 'send-mail');
            }
        }
    }
}
</code></pre>
<p><strong>modules/checkdate/src/templates/emails/_email.twig</strong></p>
<pre class="lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;UTF-8&quot;&gt;
        &lt;title&gt;Email Template&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;p&gt;{{ title }},&lt;/p&gt;
        &lt;p&gt;This is your custom email content.&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>config/app.php</strong></p>
<pre class="lang-php prettyprint-override"><code>&lt;?php
/**
 * Yii Application Config
 *
 * Edit this file at your own risk!
 *
 * The array returned by this file will get merged with
 * vendor/craftcms/cms/src/config/app.php and app.[web|console].php, when
 * Craft's bootstrap script is defining the configuration for the entire
 * application.
 *
 * You can define custom modules and system components, and even override the
 * built-in system components.
 *
 * If you want to modify the application config for *only* web requests or
 * *only* console requests, create an app.web.php or app.console.php file in
 * your config/ folder, alongside this one.
 */

use craft\helpers\App;
use modules\checkdate\CheckDate;

return [
    'id' =&gt; App::env('APP_ID') ?: 'CraftCMS',
    'modules' =&gt; [
        'check-date' =&gt; CheckDate::class
    ],
    'bootstrap' =&gt; [
        'check-date'
    ],
];
</code></pre>
<p><strong>composer.json</strong></p>
<pre class="lang-json prettyprint-override"><code>    &quot;autoload&quot;: {
        &quot;psr-4&quot;: {
            &quot;modules\\checkdate\\&quot;: &quot;modules/checkdate/src/&quot;
        }
    }
</code></pre>
<p>How do i access my template?</p>
<p>Thanks.</p>

<templating><php><emails><modules>
Unable to find custom template when using a console controller inside a module
# Unable to find custom template when using a console controller inside a module

(Asked by: username_0 on 2024-03-21)

I'm creating my first module. Inside this module, I check a few entries for some conditions, and if all conditions are met, I want to send out a personal email.
Everything worked, but I wanted to clean up my code and put the email body inside a template file so it's more organized. This is where I'm having trouble.
Whenever I run the command ./craft check-date/send-mail, I keep getting the following error:
Unable to find the template “checkdate/emails/_email.twig”.

Here is a striped down version of my module:
modules/checkdate/src/CheckDate.php
request->isConsoleRequest) {
            $this->controllerNamespace = 'modules\\checkdate\\console\\controllers';
        } else {
            $this->controllerNamespace = 'modules\\checkdate\\controllers';
        }

        parent::init();
    }
}

modules/checkdate/src/console/controllers/SendMailController.php
section('dates')
            ->fieldSendedMail(false)
            ->all();

        foreach ($entries as $entry) {

            // Variables
            $title = $entry->title;

            // Send email with HTML body
            try {
                // Render the HTML template with variables
                $htmlBody = Craft::$app->view->renderTemplate(
                    'checkdate/emails/_email.twig',
                    [
                        'name' => $title
                    ]
                );
                $mailer = Craft::$app->getMailer();
                $message = new Message();
                $message->setTo('recipient@example.com');
                $message->setSubject('Subject of the Email');
                $message->setHtmlBody($htmlBody);
                $mailer->send($message);
            } catch (InvalidConfigException $e) {
                // Handle any exceptions that occur during email sending
                Craft::error('Error sending email for entry ' . $entry->id . ': ' . $e->getMessage(), 'send-mail');
            }
        }
    }
}

modules/checkdate/src/templates/emails/_email.twig


    
        
        Email Template
    
    
        {{ title }},
        This is your custom email content.
    


config/app.php
 App::env('APP_ID') ?: 'CraftCMS',
    'modules' => [
        'check-date' => CheckDate::class
    ],
    'bootstrap' => [
        'check-date'
    ],
];

composer.json
    "autoload": {
        "psr-4": {
            "modules\\checkdate\\": "modules/checkdate/src/"
        }
    }

How do i access my template?
Thanks.

**Tags:** templating, php, emails, modules

---

## 2 answers

### Answer by username_1 on 2024-03-21. Score: 2 | Accepted answer

Unlike plugins, modules have to register their own template root.
Stick something like this in your module's init() method:
use yii\base\Event;
use craft\events\RegisterTemplateRootsEvent;
use craft\web\View;

...

Event::on(
    View::class,
    View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
    function (RegisterTemplateRootsEvent $e) {
        if (is_dir($baseDir = $this->getBasePath() . DIRECTORY_SEPARATOR . 'templates')) {
            $e->roots[$this->id] = $baseDir;
        }
    }
);

This will register a template root 'check-date' (i.e. the ID for your module, as per the config in your config/app.php file), that points to the modules/checkdate/src/templates folder.
This means that you'll also have to correct your email template path from
'checkdate/emails/_email.twig'

to
'check-date/emails/_email.twig'

It's also, generally, a good idea to be explicit about the view mode when rendering custom templates. In this case, you'll want Craft to use the 'CP' view mode, so the full View::render() method call should look like this:
use craft\web\View;

...

$htmlBody = Craft::$app->view->renderTemplate(
    'check-date/emails/_email.twig',
    [
        'name' => $title
    ],
    View::TEMPLATE_MODE_CP
);

> **Comments:**
> **username_0**: Couldn't find that myself! Thanks for clearing that out, works as expected now.  

---

### Answer by username_2 on 2024-03-21. Score: 1

I believe you'll need to switch the templateMode to be TEMPLATE_MODE_SITE in your call to renderTemplate() -- otherwise it'll default to attempting to load templates from the main Craft app.
$htmlBody = Craft::$app->view->renderTemplate(
    'checkdate/emails/_email.twig',
    ['name' => $title],
    \craft\web\View::TEMPLATE_MODE_SITE
);

https://docs.craftcms.com/api/v4/craft-web-controller.html#method-rendertemplate

> **Comments:**
> **username_0**: Hi James,  I'm a step closer, but now it will look in Craft's main template folder. Is there a way to keep everything inside the module folder?  So with you're snippet it looks for /templates/checkdate/emails/_email.twig -> works well tho.  But can i make it so it uses /modules/checkdate/src/templates/checkdate/emails/_email.twig instead?  
> **username_1**: This answer assumes that the `email.twig` template is in the _site_ template folder (i.e. `templates/checkdate/emails/_email.twig`). I believe OP wants to render the template from a folder inside their module's folder, in which case `TEMPLATE_MODE_SITE` is wrong.  
> **username_2**: Ah right I hadn't considered that approach. Interesting. Personally if it's a _module_ I'd prefer to keep all my templates together (especially since emails would likely `extend` some shared email layout templates. Only reason I'd consider bundling it together would be if it's a _plugin_ to be distributed.  
> **username_1**: @JamesSmith That's completely valid IMO. I've done both; the approach typically depends on how tightly coupled the module is to the actual site/build. For modules that are mostly self-contained, I think it's nice to keep everything pertaining to that module, including templates, in the module source folder.  

---

