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:

  • Drupal 8 is object oriented and Drupal 7 is primarily procedural. This means that a lot of your code will now be in classes rather than simple functions. This is going to be overwhelming for many, but will certainly make module development more flexible and you will upgrade your skills to modern programming practice in the process
  • Each class will need a namespace. Namespacing ensures that your class does not conflict with another class of the same name. You can think of it like a directory in a file system. You can have two files with the same name in different directories.
  • Drupal uses and extends external libraries
  • Drupal 8 uses .yml files instead .info files. .yml files are also used to define menu items, tabs etc.
  • You don’t need to use hook_menu() to map a URL to a callback function. This is now taken care of by a menu class and a route.
  • Drupal 8 uses plugins which provide swappable pieces of functionality. For more information on plugins - check out Joe Shindelar’s explanation on Drupalize.me
  • Drupal 8 uses PHP annotations. Annotations are included in PHP comments and hold vital information about a class. The Drupal 8 plugin system uses them to describe plugins and they are used by Drupal to discover your plugin.
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 new directory in /sites called all
  • Inside all create a directory modules
  • Create the directory called first_module inside the all directory

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.

  • Create first_module.info.yml in the root of the first_module directory.
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 one called first_module.module in the root of the first_module directory.

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 folder inside the first_module module folder called src, which is short for source.

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:

  • Create a folder within src called Controller.
  • Within the Controller folder, create a file called FirstController.php.

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.

  • Create a file called first_module.routing.yml
  • Add the following code to first_module.routing.yml
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.

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.

  • In your module root, create first_module.links.menu.yml
  • Add the following:
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
  • Clear the cache and then you should see the menu item under configuration -> development.
  • Click on the menu item and it will take you to /first.

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.

  • To get started, create a new directory in the module’s src folder called Plugin. This is where you can store all of your plugins for this module.
  • Then create a folder called Block. A block is a plugin type.
  • And inside the Block folder, create a file called HelloBlock.php.

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.

  • Add the following comment, above class HelloBlock extends BlockBase :
/**
 * 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.