Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
May 27 2015
May 27


In this article we are going to develop a full module. This is a step by step example. This module will give users the ability to create single or multi-step forms. I will call this module: WEBFORM.

Built and tested on Drupal: 8.0.0-dev, PHP : 5.4.24 and Apache.

This is not intended to replace or upgrade the drupal 7 web form module but instead it is a module to show case how the concept of drupal 7 web form module can be achieved using plugins/entities OR how these forms can be built using the already existing fields that are already provided in the Drupal 8 core. I won’t build the whole concept of drupal 7 web form but i will develop only parts of that module that i find interesting.

Here are some of the things that will be covered.

  • The module’s folder structure.
  • The basic files needed for a module to be installed
  • Installation of custom tables and implementing the {MODULE_NAME}.schema.yml
  • Defining entities and bundles.
  • Controlling access to the created entities and routers.
  • Creating routers and controllers
  • Defining static and dynamic permissions.
  • Defining static and dynamic routers
  • Using TAGS and the necessary tag options when exposing a class as a service.
  • Exposing defined classes of interest as services.
  • Creating forms.
  • Creating custom fields.
  • Adding a custom administration link to a Drupal administration UI or any other existing page.
  • Adding action links on a page.
  • Adding twig module specific templates.
  • Exposing the entity data to views.

Let’s start the game…

The module structure

I will put the module in drupal8_root_folder/modules. Inside the modules folder create a custom sub-folder where we shall put all our custom made modules i.e modules/custom Inside the custom folder go on to create the sub-folders below.

webform/config/schema webform/src/Access webform/src/Controller webfom/src/Entity webform/Plugin webform/Plugin/Field webform/Plugin/Field/FieldFormatter webform/Plugin/Field/FieldType webform/Plugin/Field/FieldWidget webform/templates

I will explain what to put in the folders as we develop.

NB: Take note of the case sensitivity of the folder names. Your classes may not load if that is not taken into account.

Basic module files and installation

To enable and install our module we only need webform.info.yml. So go on to create that file and inside it add the code below.

name: Webform
description: 'Create custom web forms.'
package: Custom
type: module
version: 0.1
core: 8.x

At this time you may go to the module’s page : admin/modules And enable the module. But because we we want to add custom tables you may wait till we have described the tables.

Note: no more info file

You are no longer required to add a *.module file to enable your module. The *.info file is gone. No longer applicable to Drupal 8 modules.

The info file is now a yml file.

Create or add a webform.install file in the webform folder. Inside that file implement the hook_schema(). Inside the schema describe two tables.

Sample code:

$schema = array();
$schema['webform'] = array(/* webform fields  */);
$schema['webform_field_data'] = array(/* Webform field storage  */);

Note that I have 2 tables. The first table “webform” will store general entity data like uuid, bundle type etc. This is what will be used as the “base table” of our webform entity. The second table “webform_field_data” will store other field entity data like status, created etc…… This is what will be used as the “data table” of the webform entity.

Note: use of one table

You may as well store all the entity data in one table. I am choosing this approach just because of trying out new ways of doing stuff and again to have control of my table data. i don’t like putting so many fields in one table.

Now go on to enable the module. The fun has just begun…

Defining entities and bundles

We are going to create two types of entities. One is a ConfigEntityType and the other is a ContentEntityType. As the name suggests the config entity type will store all the admin configurations and the content entity type will store data submitted by the users. In this case the content entity type will be the bundle of the config entity type.

Let’s call our config entity type: webform_type and then call the content entity type: webform So let’s go on to create the necessary files:

Add the files below.

  • webform/src/Entity/WebformType.php
  • webform/src/Entity/Webform.php

Inside the WebformType.php declare the ConfigEntityType as below.

 * Defines the Webform type configuration entity.
 * @ConfigEntityType(
 *   id = "webform_type",
 *   label = @Translation("Webform type"),
 *   handlers = {
 *     "form" = {
 *       "add" = "Drupal\webform\WebformTypeForm",
 *       "edit" = "Drupal\webform\WebformTypeForm",
 *     },
 *     "list_builder" = "Drupal\webform\WebformTypeListBuilder",
 *   },
 *   admin_permission = "administer webform types",
 *   config_prefix = "type",
 *   bundle_of = "webform",
 *   entity_keys = {
 *     "id" = "type",
 *     "label" = "name"
 *   },
 *   links = {
 *     "edit-form" = "/admin/structure/webform-types/manage/{webform_type}",
 *     "delete-form" = "/admin/structure/webform-types/manage/{webform_type}/delete",
 *     "collection" = "/admin/structure/webform-types",
 *   }
 * )

class WebformType extends ConfigEntityBundleBase {

Inside the class add all the necessary logic specific to the entity. For example in that class I added custom button configurations to be used on the multi step web forms.

protected $lable_previous = 'Previous';
  protected $lable_next = 'Next';
  protected $lable_cancel = 'Cancel';
  protected $lable_submit = 'Save';

Description of the annotation properties:

handlers.form.add : build a form for adding a web form type. handlers.list_builder : Build a page that will list all the web form types in the system config_prefix : A string that will be used in the yml files and also in code when accessing this config type properties or settings e.g {MODULE_NAME}.config_prefix.config_entity_type i.e webform.type.contact

That is if we have created or added a config web form type with the name “contact”.

bundle_of: This points to the content entity type that will act as the bundle.

The other properties are self explanatory e.g the links.edit-form

Define all your web form type entity fields in a {MODULE_NAME}.schema.yml Add the file : webform/config/schema/webform.schema.yml Describe the fields under the key: webform.type.*:

See /webform.schema.yml of how these fields were defined.

Things to note in the file.

The parent key is : {MODULE_NAME}.{config_prefix}.* i.e webform.type.* Note the type is config_entity i.e the type of entity.

Map the custom entity type fields e.g

          type: text
          label: 'The lable of the next button'

This is done for all form fields we add to the config entity form and whose values we want to store.

Inside the Webform.php declare the contentEntityType.

 * Defines the webform entity class.
 * @ContentEntityType(
 *   id = "webform",
 *   label = @Translation("Webform"),
 *   bundle_label = @Translation("Webform type"),
 *   handlers = {
 *     "access" = "Drupal\webform\WebformAccessControlHandler",
 *     "storage" = "Drupal\webform\WebformStorage",
 *     "form" = {
 *       "default" = "Drupal\webform\WebformForm",
 *     },
 *   },
 *   base_table = "webform",
 *   data_table = "webform_field_data",
 *   translatable = TRUE,
 *   entity_keys = {
 *     "id" = "wid",
 *     "bundle" = "type",
 *     "label" = "title",
 *     "langcode" = "langcode",
 *     "uuid" = "uuid"
 *   },
 *   bundle_entity_type = "webform_type",
 *   field_ui_base_route = "entity.webform_type.edit_form",
 *   permission_granularity = "bundle",
 * )

class Webform extends ContentEntityBase { ...

Description of the annotation properties

handlers.access : add logic to deny all allow access to certain routers of the web form content entity. handlers.storage : Change the way we want some of our web form field types are saved e.g this is used to override how the web form grid field type data is saved in the database. field_ui_base_route : This takes in a named router that holds the path to where we add,edit and delete (manage) fields of the entity. permission_granularity: This gives us the possibility to automatically generate permissions for all the added entity types generically.

Describing custom database table fields fields to Drupal core.

Because we have added custom tables that are to be used in our content entity, it’s important to inform the Drupal core what type of fields they are. This we do by implementing the baseFieldDefinitions method. Here we are basically creating actual Drupal core fields from our table field names e.g In the table “webform_field_data” we added a UID field that references the account/user entity of the user who has submitted the form. So in this field we go on to tell Drupal that the field UID is a reference to another entity hence the code below:

$fields['uid'] = BaseFieldDefinition::create('entity_reference')
        ->setDescription(t('The username of the user that submitted the webform.'))
        ->setSetting('target_type', 'user')
        ->setSetting('handler', 'default') ...

More of this can be seen in the code its self.

Things to take note of when declaring the entity types

  • Annotations are used to add metadata.
  • Don’t forget your name space declaration
  • Include all the necessary classes used in your code using the USE keyword.
  • Regularly empty cache as you develop so as to see new changes.
  • Describe your custom table fields by implementing the baseFieldDefinitions method.

Controlling access to the created entities and routers

From the annotation properties we defined an access handler with a value: “Drupal\webform\WebformAccessControlHandler”.

Create a new php file as shown below. webform/src/WebformAccessControlHandler.php

The code looks like:

class WebformAccessControlHandler extends EntityAccessControlHandler {

In the class implement the access methods e.g i override the create access method so as to allow submission of the web form. In here i check if the user/account object has the “submit $entity_bundle webform data” or “submit any web form” permission. Then giving access to the user to submit a web form.

Basically I am dynamically allowing access according to a certain condition. Cool… Dive into the parent class and see allot that core is already doing for us. Add logic and override as you wish to control access.

Note that those are custom permissions defined by our custom module. More on that in the next section.

Defining static and dynamic permissions

Static permissions: Create and add permissions in the file below.



administer webform types:
  title: 'Administer webform types'
  description: 'Perform tasks across all webform types.'
  restrict access: true

Dynamic permissions

These are permissions added automatically as a user adds new web form types e.g when some one adds a new web form type : contact. New contact permissions responsible for submitting, viewing etc… of that web form will be added. To be able to generate those permissions you need to add a property or key “permission_callbacks” to the {MODULE_NAME}.permissions.yml file Under that property add the necessary classes and the corresponding methods that are responsible for generating the permissions. Add this property or key.


  • \Drupal\webform\WebformPermissions::webformTypePermissions

The webformTypePermissions method will generate the necessary permissions depending on the logic added.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web