Module folder

Custom and contributed modules are placed in the /modules directory. You can use subdirectories so let’s create a subfolder /modules/custom and put the module there.

1
2
mkdir modules/custom
mkdir modules/custom/mymodule

In Drupal 7 /modules directory was reserved for core modules but in Drupal 8 it is used for custom and contributed modules. The old location sites/all/modules still works though. Core modules are now found in the /core directory.

Note: if you create /modules/contrib folder, then Drush will download modules there.

.Info file

.info file provides metadata that can be used elsewhere. It is required for Drupal to know about the module.

Create an info file modules/custom/mymodule/mymodule.info.yml with these lines:

1
2
3
4
5
name: My Module
description: A simple module.
package: Custom
type: module
core: 8.x
name Displayed in admin/modules/ (required)
description Displayed in admin/modules/ (required)
package You can group modules withfor easier management (optional)
type Type of the extension (module, theme or profile) (required)
version The core version your module is compatible with (required)

Flush caches and you should see the module in /admin/modules:

It doesn’t do anything yet.

Routing

Create a routing file modules/custom/mymodule/mymodule.routing.yml with these lines:

1
2
3
4
5
6
7
mymodule.content:
  path: '/mymodule'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyModuleController::content'
    _title: 'My Module'
  requirements:
    _permission: 'access content'
mymodule.content route name with module name as prefix
path defines the route URI.
defaults specify the default properties of the route.
_permission User has to have access content permission to access this route in /mymodule.

This invokes the content method we will define in MyModuleController.php controller and sets page title as ‘My Module’.

_controller value is not a path but a namespaced class defined by http://www.php-fig.org/psr/psr-4/ standards.

Controller

Controller is used to execute the logic.

Create a folder for the controller:

1
2
mkdir modules/custom/mymodule/src
mkdir modules/custom/mymodule/src/Controller

A module has a namespace derived from its module name. For this it would be Drupal\mymodule. This is mapped to the /mymodule/src/ folder.

Create a controller file modules/custom/mymodule/src/Controller/MyModuleController.php with these lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;

class MyModuleController extends ControllerBase {

  /**
   * Provide markup.
   *
   * @return array
   */
  public function content() {
    return array(
      '#type' => 'markup',
      '#markup' => t('My custom markup.'),
    );
  }
}

Enable the module and flush caches:

1
2
drush -y en mymodule
drush cr

Navigate to /mymodule and you should see this:

So the file:

modules/custom/mymodule/src/Controller/MyModuleController.php

is loaded and it’s content method called because of the _controller value in the routing file:

_controller: ‘\Drupal\mymodule\Controller\MyModuleController::content’

.module file

You can also add a .module file for hooks. It is a good practice to add at least the help_hook to this file.

Create a file modules/custom/mymodule/mymodule.module with these lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @file
 * Main hooks for MyModule module.
 */

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function mymodule_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.mymodule':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Lorem ipsum. For more information, see the online <a href=":url">documentation</a>', array(
          ':url' => 'http://www.wdtutorials.com',
        )) . '</p>';
      return $output;
  }
}

Flush caches.

This will provide a link to the help page:

Visit admin/help/mymodule to see the help page:

Folder structure

The folder structure should now look like this: