Profile picture for user admin
Daniel Sipos
24 Jan 2017

Drupal 8 comes with a funky new feature that allows admins and editors to customize entity forms in more than one variant. That means they can have, for different purposes, different form modes that feature different fields, with different form display configuration, etc. Nice, right?

Although you can do this in Drupal 8, not much use has yet been made of these form modes. So if you create a form mode, you cannot easily render it. Even more so, Drupal core doesn't actually make use of them.

In this article we will, however, look at how we can programatically render entity forms which use a form mode that had been created by an admin in the UI. First though, how do we render a regular entity form in Drupal 8?

Regular entity forms

$form = \Drupal::service('entity.form_builder')->getForm($entity);

$form is now a renderable array for the entity passed to the getForm() method of the EntityFormBuilder service. The entity itself can be an existing one (which will show you the edit form with values pre-filled) or a new one you just created programatically.

It's that simple.

The EntityFormBuilder::getForm() method takes a second parameter though, namely $operation. Apart from the variable name, this reminds me of how an entity itself is rendered, the second parameter to the build method being a view mode. So you'd expect in this case $operation to be a form mode, right? Well, wrong. Ish.

When an entity is defined, for example Node, under the handlers key of the annotation we have some form handlers defined. Those are what the $operation parameter expects. So for example, if we want the delete form of the Node entity, we pass delete and we get a different form. Otherwise, we fallback to the default one which is used for create/edit (although we can also specify edit but it maps to the same class anyway).

Custom form modes

Continuing with the Node example, the entity type defines three handlers (operations): default, delete, edit. That is not a lot. Other entity types can define more or less and can be differently named.

So if we create a form mode in the UI, whose machine name ends up being create_editors (for example), how do we go about rendering the entity form for nodes using that form mode? Passing that machine name as the second parameter by itself won't do the trick because the Node entity has not defined that handler, and more importantly, it doesn't map to an actual form class.

To fix this, we can alter the entity definition and add our own form handler named as our custom form mode, mapped to the default Form class of the Node entity. Here is a snippet of code, sprinkled with explanatory comments, that takes care of our Node entity for a custom form mode called create_editors:

/**
 * Implements hook_entity_type_alter().
 */
function example_module_entity_type_alter(array &$entity_types) {
  // We get all form modes, for all entities, and loop through them.
  $form_modes = \Drupal::service('entity_display.repository')->getAllFormModes();
  foreach ($form_modes as $entity_type => $display_modes) {
    if ($entity_type !== 'node') {
      // We are only interested in adding a handler to the Node entity.
      continue;
    }

    $node_type = $entity_types[$entity_type];
    foreach ($display_modes as $machine_name => $form_display) {
      if ($machine_name !== 'create_editors') {
        // We are only interested in adding this form mode to the definition.
        continue;
      }
      // We get the default handler class. It will be enough for us.
      $default_handler_class = $node_type->getHandlerClasses()['form']['default'];
      // We set the form class, keyed by the machine name of our custom form mode.
      $node_type->setFormClass($machine_name, $default_handler_class);
    }
  }
}

So basically, we are supplementing the Node form handlers with a new one called create_editors which maps to the default form class, in this case Drupal\node\NodeForm. The form class itself is not so important because after it is built, if there is a form mode with that name, it will be used to override the default form with all the configuration of that form mode. So building our node form with our custom form mode would now go like this:

$form = \Drupal::service('entity.form_builder')->getForm($entity, 'create_editors');

And that is pretty much it. Of course you can loop through all the entity types and add all form modes if you want. But I always like to inject my custom functionality only to the extent I need, touching as little as possible.

Let me know how this works for you.

Profile picture for user admin

Daniel Sipos

CEO @ Web Omelette

Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.

Contact us

Comments

nikathone 24 Jan 2017 15:47

Using rendered form modes in a multistep form

Great article as always! I was wondering how someone can go about using various form modes for an entity in a multistep form created following https://www.sitepoint.com/how-to-build-multi-step-forms-in-drupal-8. For example, someone could have step1, step2 etc. form modes and load them at each step respectively.

Thanks

Daniel Sipos 24 Jan 2017 15:33

In reply to by nikathone (not verified)

I guess for each step you'd

I guess for each step you'd load the relevant form mode and save the entity at each step. This way the entity gets saved and increasingly more populated with each step in the process.

TT 26 Jan 2017 17:15

typo

In text and in code machine names of form modes are different
create_editors != editors_create

benjy 27 Jan 2017 01:00

I'm not sure why you need all

I'm not sure why you need all the looping? You're making hard-coded assumptions about the node entity type and the create_editors form mode with the loops anyway.

$node_type = $entity_types['node'];
$default_handler_class = $node_type->getHandlerClasses()['form']['default'];
$node_type->setFormClass('create_editors',  $default_handler_class);
Daniel Sipos 28 Jan 2017 18:54

In reply to by benjy (not verified)

You are right. The looping is

You are right. The looping is not really necessary. Truth is, I had this done dynamically for multiple entities where I did loop but then I simplified things for the article. The main idea comes across though even with the loop :)

Cheers!

JD Leonard 17 Feb 2017 00:00

A simpler approach

Here's a nice simple approach.

/**
 * Implements hook_entity_type_build().
 */
function example_module_entity_type_build(array &$entity_types) {
  $entity_types['node']->setFormClass('create_editors', 'Drupal\node\NodeForm');
}
G Birch 16 Feb 2020 02:28

In reply to by JD Leonard (not verified)

breaks install

Nice and simple. But this hook is called during install from configuration, at a point where $entity_types['node'] may not yet exist. You have to wrap this statement in an if (isset($entity_types['node']) to prevent fatal errors.

Mike 25 Mar 2017 13:45

What are we getting?

We are getting a form object I presume, with getForm(), but this doesn't explain how to render it.

Daniel Sipos 29 Mar 2017 12:59

In reply to by Mike (not verified)

Have you tried it to see what

Have you tried it to see what you are getting? It should return the form render array to be rendered.

woprrr 26 Jan 2018 09:50

Form mode modules

Hello great article :) I love this subject too and really great to add just a part to talk about modules provided an « out of the box » and api for développer to manage and use form modes like Form mode manager

Peter 22 Feb 2018 20:10

any idea?

I use your line above to get rendered form, preceded by call to get blank entity:

use Drupal\reserve\Entity\ReserveReservation;
$entity = ReserveReservation::create();
$form = \Drupal::service('entity.form_builder')->getForm($entity, 'add');

I can see that my $entity is there but the getForm call generates:

Drupal\Component\Plugin\Exception\PluginNotFoundException: The "" entity type does not exist. in Drupal\Core\Entity\EntityTypeManager->getDefinition() (line 133 of E:\www\drupal8\core\lib\Drupal\Core\Entity\EntityTypeManager.php).

Any idea what is missing? I assume something missing from entity definition (btw, the entity works fine from the UI - all CRUD operations work as expected).

Nikolay 17 Apr 2019 16:05

déjà vu

It is funny. it is already the third time I'm facing the same error that the entity doesn't specify the form and every time I end up on this article :) I always forget to define hook_entity_type_alter. I have to remember it finally. Thanks for the article

sime 07 Oct 2020 15:07

I want to swap out my form

I want to swap out my form view mode based on a field value, but seems this is all or nothing per bundle?

David 08 Jan 2021 15:23

How to change for only specific content type

This changes the form for all nodes. How would you change for content type A only?

Add new comment