Blair Wadman 9 minute read

Create your first Drupal 8 module

Drupal 8 is (almost) here and if you are a developer, it is time to start learning Drupal 8 module development. Module development in Drupal 8 represents a major shift from Drupal 7. This is the first tutorial in a series where I’ll be going through the process of learning Drupal 8 development. The module that we create in this tutorial is as simple as it can be and is aimed at helping you get a feel for the module structure.

Make it easier to follow along with this post by downloading the source code

Before we get started, let’s touch on some of the major differences with Drupal 7 module development. Below is a list of concepts that will be used in this tutorial:

Note: A lot of the code written in this tutorial can be generated by the Drupal Console using generate:module, generate:controller and generate:plugin:block commands (thanks Jesus Manuel Olivas for the tip), so you don't actually have to code it yourself every time you create a new custom module. But for the purposes of learning, I like to do it do by hand first!

Name your module

As with Drupal 7, the first job is to name the module. We need to create a machine name which will be used throughout the system. Machine names are used in functions and methods in your module. For more information on naming your module, check out [naming and placing your Drupal 8 module] on drupal.org.

I’m going to call this module First Module with a machine name of first_module.

Create the module folder

In Drupal 7, core modules go in the /modules directory and you should put your custom and contrib modules in the sites/all/modules directory or sites/sitename/modules directory. In D8, core modules go into the /core directory so you can put your contrib and custom modules in the /modules directory. I’m going to stick with the Drupal 7 practice and put them in sites/all/modules.

Create a info yaml file

You need to create an info yaml file to tell Drupal that your module exists. This is similar to creating a .info file in Drupal 7.

The filename should be the machine name of your module with the .info.yml extension. In this case, it will be first_module.info.yml.

name: First Module
description: An experimental module to build our first Drupal 8 module
package: Custom
type: module
version: 1.0
core: 8.x

More information on .yml files.

Create a .module file

In Drupal 7, the .module is required even if it doesn't contain any code. in Drupal 8, it is optional. I'm going to create one which can be used later if you need to implement hooks.

Create the src directory

We need to create a subdirectory within the module directory for the controllers, plugins, forms, templates and tests. This subdirectory should be called src, which is short for source. This will allow the controller class to autoload, which means you do not have to explicitly include the class file.

Create a basic controller

Controllers do most of the work in a typical MVC application. Here are the steps to create the basic controller for the module:

In FirstController.php, we will create a simple “hello world” message so that we know it is working.

<?php
/**
 * @file
 * Contains \Drupal\first_module\Controller\FirstController.
 */

namespace Drupal\first_module\Controller;

use Drupal\Core\Controller\ControllerBase;

class FirstController extends ControllerBase {
  public function content() {
    return array(
      '#type' => 'markup',
      '#markup' => t('Hello world'),
    );
  }
}

Add a route file

The controller we created above will not do anything at this stage. We need to wire it up to a route from the URL to the controller in order for it to be executed.

first_module.content:
  path: '/first'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
  requirements:
    _permission: 'access content'

View the content

If you haven't already done so, enable the module.

If you now go to /first, you will see the Hello World message that is being returned from the controller.

Create menu link

The route now works and returns content from the controller. But you’d need to know the URL in order to reach the content. To make this more useful, we need to add it to Drupal’s menu system. To do that, you need to create a menu .yml file.

first_module.admin:
  title: 'First module settings'
  description: 'A basic module to return hello world'
  parent: system.admin_config_development
  route_name: first_module.content
  weight: 100

And that is it, your first Drupal 8 module with a menu item that returns something!

A custom block

So far, we have a custom path and menu item which displays a title and string. Let’s do something a bit more exciting and add a custom block to the module.

First, we need to create a new plugin for the module. Plugins are new in Drupal 8 and provide swappable pieces of functionality.

In that file, we need to define the namespace and class. If you use an IDE like PHPStorm, this will be created automatically.

<?php

namespace Drupal\first_module\Plugin\Block;

class HelloBlock extends BlockBase {

}

You need to extend the BlockCase class. To do that, add the following use statement:

use Drupal\Core\Block\BlockBase;

And then change class HelloBlock to HelloBlock extends BlockBase

<?php

namespace Drupal\first_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;

class HelloBlock extends BlockBase {

}

Another new concept to Drupal that we need to use for the block is Annotations. In order for Drupal to find your block code, you need to implement a code comment in a specific way, called an Annotation.

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */

Next, we need to inherit docs from the base class and add a build method, which will return the content of the block.

class HelloBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return array(
      '#markup' => $this->t('Hello, World!'),
    );
  }
}

The full code for HelloBlock.php

<?php
/**
 * @file
 * Contains \Drupal\first_module\Plugin\Block\HelloBlock.
 */

namespace Drupal\first_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */
class HelloBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return array(
      '#markup' => $this->t('Hello, World!'),
    );
  }

}

Place block

Next you need to clear the cache. And then to add the block to a region, head on over to the blocks admin page. To add the block to the sidebar first region, click on the Place block.

Place a block button

A modal will appear with a list of available blocks. Search for hello to find the Hello block you just created. Click the Place block button in the operations column.

Hello block in the block listings

You will then get a configure block form. Save that.

Configure block form

Then scroll to the bottom of the blocks layout page and click save blocks. You should now see the block in the sidebar left of your site.

Hello block in the sidebar

Overview of the file structure

Once you have completed the above steps, you should have a file structure that looks like this:

Wrapping up

In this tutorial, you have created a basic Drupal 8 module, mapped a URL to a controller which returns a string and created a custom block. Don’t worry if the concepts are totally alien at this stage. Just keep practicing and over time, everything will become clear. And remember, you are not alone - the transition to Drupal 8 is a journey the entire Drupal community is embarking on.