Create your first Drupal 8 module

Learn more

Master Drupal Development: Get the book to scale the learning curve faster

Start learning Drupal 8 development today: Get the free 7 lesson course

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.

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.
  1. name: First Module
  2. description: An experimental module to build our first Drupal 8 module
  3. package: Custom
  4. type: module
  5. version: 1.0
  6. 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.

  1. /**
  2. @file
  3. Contains \Drupal\first_module\Controller\FirstController.
  4.  */
  5.  
  6. namespace Drupal\first_module\Controller;
  7.  
  8. use Drupal\Core\Controller\ControllerBase;
  9.  
  10. class FirstController extends ControllerBase {
  11. public function content() {
  12. return array(
  13. '#type' => 'markup',
  14. '#markup' => t('Hello world'),
  15. );
  16. }
  17. }

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
  1. first_module.content:
  2. path: '/first'
  3. defaults:
  4. _controller: 'Drupal\first_module\Controller\FirstController::content'
  5. _title: 'Hello world'
  6. requirements:
  7. _permission: 'access content'

View the content

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.

  • In your module root, create first_module.links.menu.yml
  • Add the following:
  1. first_module.admin:
  2. title: 'First module settings'
  3. description: 'A basic module to return hello world'
  4. parent: system.admin_config_development
  5. route_name: first_module.content
  6. 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.

  1. namespace Drupal\first_module\Plugin\Block;
  2.  
  3. class HelloBlock {
  4.  
  5. }

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

  1. namespace Drupal\first_module\Plugin\Block;
  2. use Drupal\Core\Block\BlockBase;
  3. class HelloBlock extends BlockBase {
  4.  
  5. }

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 :
  1. /**
  2.  * Provides a 'Hello' Block
  3.  *
  4.  * @Block(
  5.  * id = "hello_block",
  6.  * admin_label = @Translation("Hello block"),
  7.  * )
  8.  */

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

  1. class HelloBlock extends BlockBase {
  2. /**
  3.   * {@inheritdoc}
  4.   */
  5. public function build() {
  6. return array(
  7. '#markup' => $this->t('Hello, World!'),
  8. );
  9. }
  10. }

Now that you have defined the block, head over to the blocks page (/admin/structure/block). On the right hand side, you will see the block listed under the heading First Module. Add it to a region like Sidebar first. And now you should see the block in the left side bar!

The full code for HelloBlock.php

  1. <?php
  2. /**
  3.  * @file
  4.  * Contains \Drupal\first_module\Plugin\Block\HelloBlock.
  5.  */
  6.  
  7. namespace Drupal\first_module\Plugin\Block;
  8. use Drupal\Core\Block\BlockBase;
  9.  
  10. /**
  11.  * Provides a 'Hello' Block
  12.  *
  13.  * @Block(
  14.  * id = "hello_block",
  15.  * admin_label = @Translation("Hello block"),
  16.  * )
  17.  */
  18. class HelloBlock extends BlockBase {
  19. /**
  20.   * {@inheritdoc}
  21.   */
  22. public function build() {
  23. return array(
  24. '#markup' => $this->t('Hello, World!'),
  25. );
  26. }
  27.  
  28. }

Place block

Add 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.

Update - 4 Dec 2015

Fixed indentation with first_module.routing.yml

Update - 12 Dec 2015

Moved the comment annotation to be directly above the HelloBlock class.
Added steps on placing the block in a region.

Drupal 8: Controllers, Routes, Form classes, the death of hooks? Oh my, so many changes! What do I start?!

Learn the first steps in starting your new Drupal module in just 20 minutes per day. You’ll get your feet wet in the new world of Drupal 8 module development and get over the hardest hurdle of all - getting started without being overwhelmed.

Get the free 7 day course

Comments

Hello Blair Wadman, I would like to thank you about this tutorial. It's really helpful for me.

Blair Wadman's picture

You're welcome!

The .module file is not needed any more in D8 if it's empty.

Hi Blair,

I really appreciate your book and videos and am looking forward to learning Drupal 8! With your help and guidance, of course. OOP is not new to me, but Drupal kinda is, and as we know the Drupal way is often different than one might expect. Thank you!

Brian.

I understand Drupal 8 is a step forward, but module creation in my opinion just turned into a mess. To create a basic "hello world" module, 6 files were created, and 5 directories - granted one directory is the module folder. On top of that it's with the use of annotations...

That all being said - nice writeup Blair. Clear and concise.

hey i tried this on my drupal 8 beta 16 site. but it doesn't seem to be working. the only thing on my site showing that my module exists is in the extend with the rest of the modules. but as soon as i install it i can't clear my cache and it doesn't know the path when i navigate to it also the whole menu item doesn't show up.
http://puu.sh/kDYul/a5623e292a.png this is how i put the module files in my site.

Blair Wadman's picture

Hi Erin,

I'm not able to debug individual cases. But I am planning on publishing the source code to this module later this week, which may help you. I will attach it to this post, so check back in a few days.

I have the same problem. 8.0-rc3

Blair Wadman's picture

I'll have a look into it asap. Because of the ongoing development to D8, something may have changed since this was published. I'll update asap

First thanks for this tutorial! Was a great help.
Please correct formatting (indention) of code in first_module.routing.yml in your example.
After fixing this, the Hello World was shown with route first.

Unfortunatly could'nt see the module in admin-block-site for placing it.

Ines, thanks for the that clue - that indentation clue fixed my problem.
my first_module.routing.yml file works like this:

  1. first_module.content:
  2. path: '/first'
  3. defaults:
  4. _controller: 'Drupal\first_module\Controller\FirstController::content'
  5. _title: 'Hello word'
  6. requirements:
  7. _permission: 'access content'

I am in the same situation as you tho - I don't see the block on the /admin/structure/block page.

Blair Wadman's picture

The routing indentation issue has been been corrected.

Regarding the block not appearing - I have updated the tutorial and moved the annotation code block and added steps on how to find the block in the block page and add it to a region. Can you try again?

Please drop cache and see if your block appear.

Thanks for the example and article. I am working on it with Drupal 8.0.0 and I get the following error when I try to rebuild cache:

$ drush cache-rebuild
exception 'Symfony\Component\Yaml\Exception\ParseException' with message 'Unable to parse at line 6 (near " requirements:").' in [error]
/Users/selwyn/Sites/d8_test2/drupal/vendor/symfony/yaml/Parser.php:298

I'm guessing the issue is with line 6 of my first_module.routing.yml file which is:
requirements:
suggestions welcome
Selwyn

Blair Wadman's picture

There was an issue with the indentation in first_module.routing.yml. Can you change it to:

  1. first_module.content:
  2. path: '/first'
  3. defaults:
  4. _controller: 'Drupal\first_module\Controller\FirstController::content'
  5. _title: 'Hello world'
  6. requirements:
  7. _permission: 'access content'

Have same issue about placing my custom block on some region. Well, i can find it, but can't place it. After i click "Place block" it does nothing. Im using Drupal 8.0.0

Blair Wadman's picture

I have updated the tutorial and moved the annotation code block and added steps on how to find the block in the block page and add it to a region. Can you try again?

Thank you very much!

This is very useful tutorial, I developed my first module today.
Thanks

The tutorial excellent, do you have a plan to publish a drupal 8 module development book for programmers?

Blair Wadman's picture

Yes, I'm working on it. Should have an announcement on that by the end of this month.

The FirstController.php file text in the "Create a basic controller" section is missing a leading "<?php".

That leads to the error:

ReflectionException: Class Drupal\first_module\Controller\FirstController does not exist in ReflectionMethod->__construct() (line 128 of core/lib/Drupal/Core/Entity/EntityResolverManager.php).

Otherwise, thanks for the example.

Thank you! This resolved my issue.

Note also that the '#type' => 'markup' line is not needed in Drupal 8 and is an invalid type according to: https://www.drupal.org/node/2036237

Thank you. Everything work good. My first drupal 8 module works. Cool.

I found a typo in your code...

"Add the following comment, above class HelloBlock extends BlockBase :"

Last line:
* /

should be */ without a space...
if not, the thing is broken.

greetings,
Nico

Blair Wadman's picture

Thanks for spotting that! Corrected..

If the controller is meant to be the receiver of requests what is put into the .module file?

Blair Wadman's picture

The .module is now optional (just updated the post, it used to be required even if empty). You may still need the .module to implement one of Drupal's hooks. Drupal is slowly moving away from hooks, but they still exist. There is a list here - https://api.drupal.org/api/drupal/core%21core.api.php/group/hooks/8

Hi thanks for the module. But I want to know when you are coming with drupal 8 module development ebook like your drupal 7 master_drupal.

Blair Wadman's picture

Hi Sameer, thanks for asking - I am currently writing it. I don't have an exact release date but I'll send out an updates as to progress soon.

Nice Effort, Thanks Blair!

But the folder structure picture is looks wrong, the yml files (routing+link) should be in the module root folder.

Blair Wadman's picture

Thanks Rinto.

Yes you are right, the picture does look wrong. The picture is a screenshot of the folder structure in PHPStorm. The yml files are in the module root folder, but for some reason it looks like they are nested in the src folder. I'll do it again in a different IDE/Editor and see if it looks clearer.

Not getting any changes even after clearing the cache. Changing in url not reflecting

Thanks, I created first module in drupal 8 with the help of this article.

Thanks Blair, you got it going on.

Nice write up, I read this in conjunction with the ever excellent examples module. This tutorial really helped explain some stuff.

Blair,
Nice tutorial! I'm new to Drupal and this helped me a ton.

Hello Guys!
I have doubt in custom block.
I create a block in Plugin/Block/Hello.php
I want to display the uploaded files information like name size createdAt from the database table "file_managed" in drupal 8.
Please help me how to do that, i am facing a problem in only displaying markup code like '#markup' =&gt; t('hello world').
Here i want the files list.
Please help me.

Add new comment