Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T16:20:04.900Z
Answers.Text: Couldn't find that myself! Thanks for clearing that out, works as expected now.
Answers.UserDisplayName: Arno Ramon
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T16:10:14.173Z
Answers.LastActivityDate: 2024-03-21T16:10:14.173Z
Answers.UserDisplayName: Mats Mikkel Rummelhoff
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T16:07:30.540Z
Answers.Text: 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?
Answers.UserDisplayName: Arno Ramon
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T16:13:15.323Z
Answers.Text: 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.
Answers.UserDisplayName: Mats Mikkel Rummelhoff
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T17:08:34.790Z
Answers.Text: 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.
Answers.UserDisplayName: James Smith
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T18:28:53.680Z
Answers.Text: @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.
Answers.UserDisplayName: Mats Mikkel Rummelhoff
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-03-21T16:00:11.760Z
Answers.LastActivityDate: 2024-03-21T16:00:11.760Z
Answers.UserDisplayName: James Smith
Body: <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>
  
Tags: <templating><php><emails><modules>
Title: Unable to find custom template when using a console controller inside a module
ThreadText: # 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**
  
  ```php
  <?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->request->isConsoleRequest) {
              $this->controllerNamespace = 'modules\\checkdate\\console\\controllers';
          } else {
              $this->controllerNamespace = 'modules\\checkdate\\controllers';
          }
  
          parent::init();
      }
  }
  
  ```
  
  **modules/checkdate/src/console/controllers/SendMailController.php**
  
  ```php
  <?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()
              ->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**
  
  ```html
  <!DOCTYPE html>
  <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Email Template</title>
      </head>
      <body>
          <p>{{ title }},</p>
          <p>This is your custom email content.</p>
      </body>
  </html>
  
  ```
  
  **config/app.php**
  
  ```php
  <?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' => App::env('APP_ID') ?: 'CraftCMS',
      'modules' => [
          'check-date' => CheckDate::class
      ],
      'bootstrap' => [
          'check-date'
      ],
  ];
  
  ```
  
  **composer.json**
  
  ```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](https://craftcms.com/docs/4.x/extend/template-roots.html).
  
  Stick something like this in your module's `init()` method:
  
  ```php
  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
  
  ```php
  'checkdate/emails/_email.twig'
  
  ```
  
  to
  
  ```php
  '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:
  
  ```php
  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.
  
  ```php
  $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 username_2,  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**: @username_2 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.  
  
  ---
  
  