Drupal 8 Development, Part 2: Scaffolding a Module

This article is part of a series called Drupal 8 Development Deconstructed about custom development with Drupal 8.

Now we're going to learn about scaffolding a module using Drupal Console.

One of the components that I need to create for my project port to D8 is a taxonomy vocabulary that contains gender terms. The project I'm porting to D8 is a family tree collaboration web application, and the gender taxonomy vocabulary will be eventually linked to a content type taxonomy term reference field for a Person Profile. Before I can create the Person content type, I need to create the taxonomy vocabulary so that it is available to link as a term reference field.

I not only want to create the taxonomy vocabulary, but I also want to programmatically add the gender terms to the vocabulary, so as part of this module, we will also write some hook_install() and hook_update_n() methods to do just that.

However, before we start with our taxonomy vocabulary, let's scaffold out a module that we will use as our starting point, using Drupal Console.

Before we do that, however, let's create a couple of directories inside the "modules" folder that we'll use to store custom vs. contrib modules. The "modules" folder in Drupal 8 has been relocated, moved from "sites/all" to the Drupal root directory. You'll find the "themes" folder in the same location. Inside the "modules" folder, create two folders called "custom" and "contrib".

You will also see that the Drupal VM comes with the "devel" module pre-installed. Move the "devel" folder out of the "modules" folder, and into "modules/contrib".

Clear the cache: drush cr

Drupal Console

Drupal Console is a command line tool built using Symfony2's Console component to provide Drupal developers with some convenient commands to execute a wide variety of scripted processes on a Drupal project, including scaffolding out various modules. We'll use this tool to scaffold our module as well. Drupal Console comes pre-installed as part of the Drupal VM.

The latest version available when this blog series was started is 0.10.0.

We'll be creating a taxonomy vocabulary to use in our project, called "gender". This vocabulary will become a taxonomy term reference field on a content type.

Scaffolding your first module

Shell into your VM, and navigate to the Drupal root directory location. Inside the Drupal root directory (where the "modules" directory is located) execute the following:

drupal generate:module

You'll be prompted for a series of inputs:

  • Our module name will be "Gender Vocabulary"
  • We'll use the machine name "gen_gender_vocabulary" for our module (all of our custom modules will be prefixed with "gen")
  • Our module's description will be "Gender Vocabulary for Gen Person module" (Gen Person will be a module we will create later)
  • Our package name will be "Genealogy"
  • Our module will have one dependency: taxonomy

We'll accept the defaults for the rest of the questions.

vagrant@d8project:/var/www/sites/d8project.dev/www$ drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Gender Vocabulary

 Enter the module machine name [gender_vocabulary]:
 > gen_gender_vocabulary

 Enter the module Path [/modules/custom]:

 Enter module description [My Awesome Module]:
 > Gender Vocabulary for Gen Person module

 Enter package name [Other]:
 > Genealogy

 Enter Drupal Core version [8.x]:

 Define module as feature (yes/no) [no]:

 Do you want to add a composer.json file to your module (yes/no) [yes]:

 Would you like to add module dependencies (yes/no) [no]:
 > yes

 Module dependencies separated by commas (i.e. context, panels):
 > taxonomy

 Do you confirm generation? (yes/no) [yes]:

Generated or updated files
Site path: /var/www/sites/d8project.dev/www
1 - modules/custom/gen_gender_vocabulary/gen_gender_vocabulary.info.yml
2 - modules/custom/gen_gender_vocabulary/gen_gender_vocabulary.module
3 - modules/custom/gen_gender_vocabulary/composer.json

Drupal Console created a module directory inside "modules/custom" using our machine name for our module, and created three files for us:

  • gen_gender_vocabulary.info.yml
  • gen_gender_vocabulary.module
  • composer.json

The composer.json file contains package management metadata that will be needed if you are using Composer to manage your project (we won't be discussing Composer as part of this series). The other two files are the bare minimum necessary to create a Drupal 8 module.


This file is our .info file using the YAML format. Basically the same information we are accustomed to placing in a .info file for a module will be placed in this file. We specified as part of our metadata that this module will be dependent on the taxonomy module, so we should see that specified in the .info.yml file. If you examine the contents of gen_gender_vocabulary.info.yml you should see the following:

name: Gender Vocabulary
type: module
description: Gender Vocabulary for Gen Person module
core: 8.x
package: Genealogy
  - taxonomy

You can see the inputs we provided to the questions prompted by Drupal Console are included here. Note one additional component specified here: type. This was not part of the metadata specified in the .info file in Drupal 7, but is used in Drupal 8 to provide consistency to the .info.yml file, and allow this file to be used across various plugin components for Drupal 8 (module, theme or profile), all of which are now referred to as "extensions".

Drupal Console has conveniently formatted the .info.yml file correctly, using YAML formatting. Nice!


This file is our base module file. Currently this file will contain very little functional information, and is intended to serve as a starting point for our module development. If you open the file you will see the following:

 * @file
 * Contains gen_gender_vocabulary.module..

use Drupal\Core\Routing\RouteMatchInterface;

 * Implements hook_help().
function gen_gender_vocabulary_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the gen_gender_vocabulary module.
    case 'help.page.gen_gender_vocabulary':
      $output = '';
      $output .= '

' . t('About') . '

'; $output .= '

' . t('Gender Vocabulary for Gen Person module') . '

'; return $output; default: } } /** * Implements hook_theme(). */ function gen_gender_vocabulary_theme() { $theme = []; return $theme; }

While not necessary to create a module, Drupal Console stubs out two hooks for your module as a starting point:

  • hook_help
  • hook_theme

The hook_theme is optional, and may or may not be needed as part of your custom module. We won't be using it as part of the Gender Vocabulary module, so we'll remove it. Remove the following from the .module file, and save the .module file with the changes.

 * Implements hook_theme().
function gen_gender_vocabulary_theme() {
  $theme = [];

  return $theme;

It's good practice to include some basic help information for users about your module that is accessible through the user interface, especially if there is any additional configuration or inputs that site builders or site administrators may need to know. Any information included in hook_help will be made available through the "Help" functionality in the administrative interface, just as with previous versions of Drupal. For now, we'll accept the default stub built for us by Drupal Console.

Creating a .install file

We will be inserting some taxonomy terms programmatically as part of our custom module, which we want inserted into the Gender vocabulary when our module is installed. Just as in previous versions of Drupal, we will accomplish this through the use of a hook_install method, which will be include in a .install file.

Create a file named gen_gender_vocabulary.install in the root directory of your module (modules/custom/gen_gender_vocabulary), insert the following code, and save the file:

 * @file
 * Contains gen_gender_vocabulary.install.

 * Implements hook_install().
function gen_gender_vocabulary_install() {

 * Implement hook_uninstall().
function gen_gender_vocabulary_uninstall() {

We basically just stubbed out our hook_install() and hook_uninstall() functions.