Enhancing a Craft CMS 3 Website with a Custom Module
Tags: module, plugin, craftcms
Published: 2020-02-19 22:14:18

---
title: Enhancing a Craft CMS 3 Website with a Custom Module
published: true
date: 2017-12-30 21:42:00 UTC
tags: module,plugin,craftcms
canonical_url: https://nystudio107.com/blog/enhancing-a-craft-cms-3-website-with-a-custom-module
---

# Enhancing a Craft CMS 3 Website with a Custom Module

### Enhanc­ing your clien­t’s Craft CMS 3 web­site with a Mod­ule lets you add cus­tom func­tion­al­i­ty with­out resort­ing to using or writ­ing a plugin

Andrew Welch / [nystudio107](https://nystudio107.com)

![Craft-Cms-3-Yii2-Custom-Module](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/craft-cms-3-yii2-custom-module.jpg)

Some­times you want to enhance a client web­site with some func­tion­al­i­ty or design that’s very spe­cif­ic to that web­site. Cer­tain­ly you could do this with a cus­tom plu­g­in with scaf­fold­ing from [plug​in​fac​to​ry​.io](https://pluginfactory.io/) and fol­low­ing the [So You Wan­na Make a Craft 3 Plu­g­in?](https://dev.to/gaijinity/so-you-wanna-make-a-craft-3-plugin-76m-temp-slug-4321262) article.

How­ev­er, for many things this just seems like too much work. Maybe you just want to enhance the look of the login screen to apply a back­ground image with the clien­t’s brand. A cus­tom plu­g­in seems like a bit much.

With Craft CMS 3, Craft intro­duces the con­cept of a Mod­ule, which fits the bill per­fect­ly for this type of scenario.

## Mod­ules vs. Plugins

The pri­ma­ry dif­fer­ences between a Mod­ule and a Plu­g­in are:

- Plu­g­ins can be disabled
- Plu­g­ins can be uninstalled
- Plu­g­ins have a frame­work for Set­tings in the AdminCP

Oth­er than that, they are quite sim­i­lar. Both Mod­ules and Plu­g­ins are writ­ten in PHP, and can access the full Craft CMS APIs.

Note that you _can_ have set­tings and AdminCP sec­tions in a Mod­ule as well, but you have to ​“roll your own” via lis­ten­ing to the appro­pri­ate events, adding the appro­pri­ate routes, etc.

<aside>
                                Mod­ules are a per­fect fit when the func­tion­al­i­ty is an inte­gral part of the website
                            </aside>

Even if you don’t con­sid­er your­self a ​“PHP devel­op­er”, it’s pret­ty easy to get a sim­ple Mod­ule up and run­ning that will load some cus­tom CSS or JavaScript in the Craft AdminCP that enhances the expe­ri­ence for your client.

We’ll show you exact­ly how to do that in this article.

## Mod­ules Under the Hood

A nice way to think about Mod­ules is that they are Plu­g­ins that can’t be unin­stalled. They strike a nice bal­ance between being easy to imple­ment, and offer­ing the func­tion­al­i­ty of a plugin.

![Module-Plugin-Balance](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/module-plugin-balance.jpg)

While it’s tempt­ing to think of Mod­ules are stripped down Plu­g­ins, the real­i­ty is that Plu­g­ins are actu­al­ly built on top of Modules!

<aside>
                                So we should real­ly be think­ing of Plu­g­ins as enhanced Modules
                            </aside>

Have a look at the code for <kbd>craft\base\Plugin</kbd>:

```

/**
 * Plugin is the base class for classes representing plugins in terms of objects.
 *
 * @property string $handle The plugin’s handle (alias of [[id]])
 * @property MigrationManager $migrator The plugin’s migration manager
 *
 * @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
 * @since 3.0
 */
class Plugin extends Module implements PluginInterface
{
...

```

What this is show­ing is that Craft CMS 3 Plu­g­ins are actu­al­ly [Yii2 Mod­ules](http://www.yiiframework.com/doc-2.0/guide-structure-modules.html), but just with some enhance­ments added to them by Pix­el & Ton­ic. These enhance­ments allow plu­g­ins to be unin­stalled, have set­tings, AdminCP sec­tions, etc.

Note that you _can_ have set­tings and AdminCP sec­tions in a Mod­ule as well, but you have to ​“roll your own” via lis­ten­ing to the appro­pri­ate events, adding the appro­pri­ate routes, etc.

This fol­lows a theme that was dis­cussed in the [Set­ting up a New Craft CMS 3 Project](https://dev.to/gaijinity/setting-up-a-new-craft-cms-3-project-nfp-temp-slug-9254661) arti­cle, which is that Craft CMS 3 has been entire­ly refac­tored on top of [Yii2](http://www.yiiframework.com/doc-2.0/guide-intro-yii.html).

<aside>
                                Craft <span>CMS</span> <span>3</span> is now both a Con­tent Man­age­ment Sys­tem (<span>CMS</span>) and a Con­tent Man­age­ment Frame­work (<span>CMF</span>)
                            </aside>

This is an impor­tant point, because many cus­tom apps that would nor­mal­ly be built using a frame­work like Lar­avel very well may be built using Craft CMS 3. Check out the [REST­ful API with Craft 3](https://dotall.flipboxlabs.com/) for an exam­ple of doing just that!

![Craft-Cms-3-Content-Management-Framework](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/craft-cms-3-content-management-framework.jpg)

This means that we’ll like­ly be see­ing Craft CMS 3 being used as a frame­work & foun­da­tion for web apps that want an awe­some CMS back­end for free. But I digress…

The rest of this arti­cle dis­cuss­es a cus­tom mod­ule in detail, but you can cre­ate our own on [plug​in​fac​to​ry​.io](https://pluginfactory.io/) as well:

![Pluginfactory-Io-Custom-Modules](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_768x652_crop_center-center_100_line/pluginfactory-io-custom-modules.png)

## Set­ting Up a Site Module

So let’s talk about set­ting up an actu­al site mod­ule for our Craft web­site. All of the code list­ed here is avail­able in the [site-mod­ule](https://github.com/nystudio107/site-module) GitHub repo should you want to down­load it.

All our site mod­ule does is load an [Asset Bun­dle](https://github.com/craftcms/docs/blob/v3/en/asset-bundles.md) that con­tains CSS and JavaScript that we want loaded in the AdminCP.

This allows you to do things like have a client brand back­ground image on the login screen, or to tweak the look & func­tion­al­i­ty of the AdminCP as you see fit via CSS & JavaScript.

![Craft-Asset-Bundles](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/craft-asset-bundles.jpg)

Mod­ules can do quite a bit more than this, in fact they can do any­thing a Plu­g­in can do. But this foun­da­tion allows a fron­tend devel­op­er to enhance their clien­t’s web­site with­out need­ing to get into the nit­ty grit­ty of how the mod­ule works.

You’ll find that if you used the <kbd>composer create-project -s RC craftcms/craft PATH</kbd> com­mand that [Pix­el & Ton­ic rec­om­mends](https://github.com/craftcms/docs/blob/master/en/installation.md#2-create-a-new-craft-project) to cre­ate your new project, they’ve even pro­vid­ed a sam­ple <kbd>config/app.php</kbd> and <kbd>modules/Module.php</kbd> for you already. We’ve tweaked things a bit from this, so let’s get to it!

Here’s what the project tree looks like; again you can down­load the full source from the [site-mod­ule](https://github.com/nystudio107/site-module) GitHub page:

```

vagrant@homestead ~/webdev/craft/site-module (develop) $ tree -L 8 .
.
├── CHANGELOG.md
├── composer.json
├── config
│   └── app.php
├── LICENSE.md
├── modules
│   └── sitemodule
│   ├── CHANGELOG.md
│   ├── config
│   │   └── app.php
│   ├── LICENSE.md
│   ├── README.md
│   └── src
│   ├── assetbundles
│   │   └── sitemodule
│   │   ├── dist
│   │   │   ├── css
│   │   │   │   └── SiteModule.css
│   │   │   ├── img
│   │   │   │   └── SiteModule-icon.svg
│   │   │   └── js
│   │   │   └── SiteModule.js
│   │   └── SiteModuleAsset.php
│   ├── SiteModule.php
│   └── translations
│   └── en
│   └── site-module.php
└── README.md

13 directories, 15 files

```

If it looks com­pli­cat­ed, don’t wor­ry about it. There are actu­al­ly more orga­ni­za­tion­al fold­ers than files there! There are essen­tial­ly 3 parts to it:

1. Craft’s <kbd>config/app.php</kbd>
2. The mod­ule itself in <kbd>modules/sitemodule/src/SiteModule.php</kbd>
3. The Asset Bun­dle we load in <kbd>modules/sitemodule/src/assetbundles/SiteAsset.php</kbd>

We did­n’t have to name­space things with <kbd>sitemodule/src</kbd> but we want a fold­er to group every­thing con­tained in our mod­ule togeth­er (<kbd>sitemodule</kbd>) in case we have oth­er mod­els, and it’s a con­ven­tion to put all of our source code in a <kbd>src</kbd> sub-directory.

You could just as eas­i­ly get rid of those two direc­to­ries, and put every­thing inside of the <kbd>modules/</kbd> direc­to­ry itself.

So let’s look at these three pieces in detail:

## 1. Edit the config/app.php

The <kbd>config/</kbd> direc­to­ry has a num­ber of con­fig files that you’re used to, like <kbd>general.php</kbd>, <kbd>db.php</kbd>, etc. used for var­i­ous set­tings in Craft CMS 3. But it also can have an <kbd>app.php</kbd> con­fig file.

The <kbd>app.php</kbd> con­fig file is super-pow­er­ful, in that it allows you to _over­ride or extend any part of the Craft CMS 3 Yii2 app_. Read that again, because it’s huge. With a sim­ple con­fig file, we can extend the Yii2 app that is Craft CMS 3, or we can replace func­tion­al­i­ty entirely.

We’re just going to dip our toe into it, and add a bit of code to it to tell it about our new Mod­ule, and to load it for us.

```

<?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/main.php and [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.
 */

return [

    // All environments
    '*' => [
        'modules' => [
            'site-module' => [
                'class' => \modules\sitemodule\SiteModule::class,
            ],
        ],
        'bootstrap' => ['site-module'],
    ],

    // Live (production) environment
    'live' => [
    ],

    // Staging (pre-production) environment
    'staging' => [
    ],

    // Local (development) environment
    'local' => [
    ],
];

```

We’re giv­ing Craft the class of our mod­ule, along with the han­dle <kbd>site</kbd> to refer to it by, then we’re telling it to load it for every request via <kbd>bootstrap</kbd>.

## 2. The Mod­ule Class

Next up we have our Mod­ule class itself in <kbd>modules/sitemodule/src/SiteModule.php</kbd>. This is what is actu­al­ly loaded and exe­cut­ed on each request:

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * An example module for Craft CMS 3 that lets you enhance your websites with a custom site module
 *
 * @link https://nystudio107.com/
 * @copyright Copyright (c) 2018 nystudio107
 */

namespace modules\sitemodule;

use modules\sitemodule\assetbundles\sitemodule\SiteModuleAsset;

use Craft;
use craft\events\RegisterTemplateRootsEvent;
use craft\events\TemplateEvent;
use craft\i18n\PhpMessageSource;
use craft\web\View;

use yii\base\Event;
use yii\base\InvalidConfigException;
use yii\base\Module;

/**
 * Class SiteModule
 *
 * @author nystudio107
 * @package SiteModule
 * @since 1.0.0
 *
 */
class SiteModule extends Module
{
    // Static Properties
    // =========================================================================

    /**
     * @var SiteModule
     */
    public static $instance;

    // Public Methods
    // =========================================================================

    /**
     * @inheritdoc
     */
    public function __construct($id, $parent = null, array $config = [])
    {
        Craft::setAlias('@modules/sitemodule', $this->getBasePath());
        $this->controllerNamespace = 'modules\sitemodule\controllers';

        // Translation category
        $i18n = Craft::$app->getI18n();
        /** @noinspection UnSafeIsSetOverArrayInspection */
        if (!isset($i18n->translations[$id]) && !isset($i18n->translations[$id.'*'])) {
            $i18n->translations[$id] = [
                'class' => PhpMessageSource::class,
                'sourceLanguage' => 'en-US',
                'basePath' => '@modules/sitemodule/translations',
                'forceTranslation' => true,
                'allowOverrides' => true,
            ];
        }

        // Base template directory
        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;
            }
        });

        // Set this as the global instance of this module class
        static::setInstance($this);

        parent::__construct($id, $parent, $config);
    }

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        self::$instance = $this;

        if (Craft::$app->getRequest()->getIsCpRequest()) {
            Event::on(
                View::class,
                View::EVENT_BEFORE_RENDER_TEMPLATE,
                function (TemplateEvent $event) {
                    try {
                        Craft::$app->getView()->registerAssetBundle(SiteModuleAsset::class);
                    } catch (InvalidConfigException $e) {
                        Craft::error(
                            'Error registering AssetBundle - '.$e->getMessage(),
                            __METHOD__
                        );
                    }
                }
            );
        }

        Craft::info(
            Craft::t(
                'site-module',
                '{name} module loaded',
                ['name' => 'Site']
            ),
            __METHOD__
        );
    }

    // Protected Methods
    // =========================================================================
}

```

The <kbd>__construct()</kbd> method may look a lit­tle scary, but we’re just set­ting up a Yii2 alias to our Mod­ule’s direc­to­ry so we can use it lat­er, then set­ting things up so that our mod­ule can have trans­la­tions, and poten­tial­ly tem­plates in the AdminCP as well.

Just skip over that, and check out the <kbd>init()</kbd> method.

Here we check to make sure this is an AdminCP request (which are nev­er con­sole / com­mand line requests), and then lis­ten­ing for the <kbd>EVENT_BEFORE_RENDER_TEMPLATE</kbd> event.

This event is fired just before a Twig tem­plate is about to be ren­dered. This lets us load our Asset Bun­dle along with its CSS & JavaScript last, after every­thing else has been loaded.

This is great, because we usu­al­ly want to over­ride the look or func­tion­al­i­ty of some­thing in the AdminCP, and [CSS Speci­fici­ty](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) means that if we’re loaded last, we get a shot at doing just that.

## 3. Our Asset Bundle

An Asset Bun­dle is just a col­lec­tion of arbi­trary resources such as CSS, JavaScript, images, etc. that need to be loaded and avail­able on the fron­tend. You can read more about [Asset Bun­dles here](https://github.com/craftcms/docs/blob/master/en/asset-bundles.md).

This is what our <kbd>modules/sitemodule/src/assetbundles/SiteAsset.php</kbd> looks like:

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * An example module for Craft CMS 3 that lets you enhance your websites with a custom site module
 *
 * @link https://nystudio107.com/
 * @copyright Copyright (c) 2018 nystudio107
 */

namespace modules\sitemodule\assetbundles\SiteModule;

use Craft;
use craft\web\AssetBundle;
use craft\web\assets\cp\CpAsset;

/**
 * @author nystudio107
 * @package SiteModule
 * @since 1.0.0
 */
class SiteModuleAsset extends AssetBundle
{
    // Public Methods
    // =========================================================================

    /**
     * @inheritdoc
     */
    public function init()
    {
        $this->sourcePath = "@modules/sitemodule/assetbundles/sitemodule/dist";

        $this->depends = [
            CpAsset::class,
        ];

        $this->js = [
            'js/SiteModule.js',
        ];

        $this->css = [
            'css/SiteModule.css',
        ];

        parent::init();
    }
}

```

It just sets the <kbd>sourcePath</kbd> to our <kbd>dist/</kbd> direc­to­ry, mean­ing that every­thing under the <kbd>dist/</kbd> direc­to­ry is what should be pub­lished on the fron­tend in <kbd>web/cpresources/</kbd> in a hashed direc­to­ry name.

Then it says that we depend on the AdminCP Asset­Bun­dle being loaded already, and gives a path to the CSS & JavaScript that we want inject­ed into the AdminCP templates.

All you real­ly need to under­stand from all of this is that every­thing in the <kbd>dist/</kbd> direc­to­ry will be pub­lished in <kbd>web/cpresources/</kbd> and the CSS & JavaScript we spec­i­fied will be loaded:

```

vagrant@homestead ~/webdev/craft/site-module/modules/sitemodule/src/assetbundles/sitemodule (develop) $ tree -L 3 .
.
├── dist
│   ├── css
│   │   └── SiteModule.css
│   ├── img
│   └── js
│   └── SiteModule.js
└── SiteModuleAsset.php

4 directories, 3 files

```

So you can mod­i­fy the <kbd>Site.css</kbd> and <kbd>Site.js</kbd> to your heart’s con­tent, and it’ll be loaded by our mod­ule in the AdminCP.

## Mak­ing Com­pos­er Happy

To make Com­pos­er hap­py, we also need to make sure we have the fol­low­ing in our pro­jec­t’s <kbd>composer.json</kbd> file:

```

"autoload": {
    "psr-4": {
      "modules\\sitemodule\\": "modules/sitemodule/src/"
    }
  },

```

This just ensures that Com­pos­er will know where to find our mod­ules. You might also need to do:

```

composer dump-autoload

```

…from the pro­jec­t’s root direc­to­ry if you did­n’t already have the above in your <kbd>composer.json</kbd>, to rebuild the Com­pos­er autoload map. This will hap­pen auto­mat­i­cal­ly any time you do a <kbd>composer install</kbd> or <kbd>composer update</kbd> as well.

## Mod­ules in Action

Here’s a sim­ple exam­ple of a Mod­ule in action, on my new pod­cast web­site [dev​Mode​.fm](https://devmode.fm/):

![Devmode-Fm-Craft-Module](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x923_crop_center-center_100_line/devmode-fm-craft-module.png)

Using a lit­tle CSS, all it does is put our col­or­ful back­ground image on the login page:

```

/**
 * SiteModule CSS
 *
 * @author nystudio107
 * @copyright Copyright (c) 2017 nystudio107
 * @link https://nystudio107.com
 * @package SiteModule
 * @since 1.0.0
 */

body.login {
    background-size: 600px;
    background-repeat: repeat;
    background-image: url('/img/site/devmode-fm-light-bg-opaque.svg');
}

body.login label, body.login #forgot-password {
    background-color: #FFF;
}

```

You can of course do quite a bit more than that in a Mod­ule. I recent­ly redid the [nys​tu​dio107​.com](https://nystudio107.com) web­site you’re read­ing right now to use Craft CMS 3 & [Tail­wind CSS](https://tailwindcss.com/docs/what-is-tailwind/).

As part of that process, I rewrote a very site-spe­cif­ic Plu­g­in as a Mod­ule that loads some cus­tom CSS & JavaScript, reg­is­ters a cus­tom Redac­tor II plu­g­in, and more.

While the exam­ple pre­sent­ed here is rel­a­tive­ly sim­plis­tic, you can do things like reg­is­ter Fields, add Twig fil­ters, and oth­er such things from a Mod­ule just like you can from a Plugin.

The gen­er­al rule of thumb is that any­thing that’s very site-spe­cif­ic or ​“busi­ness logic”-ish, you might want to refac­tor as a Mod­ule. Then just check it into the web­site’s main repos­i­to­ry, rather than hav­ing it as a sep­a­rate Plugin.

Head on over to [plug​in​fac​to​ry​.io](https://pluginfactory.io/) and build your own cus­tom Craft CMS 3 module!

Viva la modularity!

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>