14

My module provides a configuration form which allows to select a content type and on submit 2 new fields will be added to the selected content types.

I want to add these fields to the content types programmatically and I tried this using hook_entity_bundle_field_info but the fields don't get saved.

How can I save the fields? And how can I delete the fields (if the user changes the content type in the configuration form)?

4 Answers 4

19

You can use this example from the node module.

This adds a body field when a content type is created and at the same time configures the display modes (view modes and form modes):

core/modules/node/node.module:

/**
 * Adds the default body field to a node type.
 *
 * @param \Drupal\node\NodeTypeInterface $type
 *   A node type object.
 * @param string $label
 *   (optional) The label for the body instance.
 *
 * @return \Drupal\field\Entity\FieldConfig
 *   A Body field object.
 */
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
  // Add or remove the body field, as needed.
  $field_storage = FieldStorageConfig::loadByName('node', 'body');
  $field = FieldConfig::loadByName('node', $type->id(), 'body');
  if (empty($field)) {
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => $type->id(),
      'label' => $label,
      'settings' => ['display_summary' => TRUE],
    ]);
    $field->save();

    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');

    // Assign widget settings for the default form mode.
    $display_repository->getFormDisplay('node', $type->id())
      ->setComponent('body', [
        'type' => 'text_textarea_with_summary',
      ])
      ->save();

    // Assign display settings for the 'default' and 'teaser' view modes.
    $display_repository->getViewDisplay('node', $type->id())
      ->setComponent('body', [
        'label' => 'hidden',
        'type' => 'text_default',
      ])
      ->save();

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
    $view_modes = \Drupal::service('entity_display.repository')->getViewModes('node');
    if (isset($view_modes['teaser'])) {
      $display_repository->getViewDisplay('node', $type->id(), 'teaser')
        ->setComponent('body', [
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ])
        ->save();
    }
  }

  return $field;
}

Edit: Code updated from 8.8.x to remove deprecated functions, see change record https://www.drupal.org/node/2835616

The field storage itself is not added or deleted because it is shared between all content types. You have to do this once when you install the module. The easiest way is to put a yml file for the storage in /config/install. Example from the node module:

core/modules/node/config/install/field.storage.node.body.yml:

langcode: en
status: true
dependencies:
  module:
    - node
    - text
id: node.body
field_name: body
entity_type: node
type: text_with_summary
settings: {  }
module: text
locked: false
cardinality: 1
translatable: true
indexes: {  }
persist_with_no_fields: true
custom_storage: false
1
  • 4
    Test coverage for field types also have loads of examples like this.
    – mpdonadio
    Dec 7, 2016 at 1:25
12

Edit: Little More Description: For adding entity type (content type, block type, product type, variation type ... ) field programmatically you need

  1. Create Field Storage Configuration: only then
  2. Create Field Configuration

You can use Drupal 8 configuration Single export which can be found here -> /admin/config/development/configuration/single/export

Selecting needed Field Storage or Field config will give you desired fields and their values. enter image description here

Next All Described Very Well

I like to add, that the node modules code uses some deprecated functions. (We are now 8.5.6).

Here's my part of the code which handles "create a field storage programmatically:

if (empty($fieldStorageMicrositesfield = \Drupal\field\Entity\FieldStorageConfig::loadByName('node', $fieldName))) {
  $fieldStorage = \Drupal\field\Entity\FieldStorageConfig::create(
    [
      'field_name'             => $fieldName,
      'langcode'               => 'en',
      'entity_type'            => 'node',
      'type'                   => 'list_string',
      'settings'               => [
        'allowed_values'          => [],
        'allowed_values_function' => 'I_used_a_custum_function_here_which_you_not_need_I_guess',
      ],
      'module'                 => 'options',
      'locked'                 => FALSE,
      'cardinality'            => -1,
      'translatable'           => TRUE,
      'persist_with_no_fields' => FALSE,
      'custom_storage'         => FALSE,
    ]
  );
  $fieldStorageSaved = $fieldStorage->save();
  if ($fieldStorageSaved) {
    $message = t(
      "Field @a storage created.",
      ['@a' => $fieldName,]
    );
  }
  else {
    $message = t(
      "Field @a storage could not be created.",
      ['@a' => $fieldName,]
    );
  }
}
else {
  $message = t(
    "Field @a storage could not be created because the field already exists",
    ['@a' => $fieldName,]
  );
}

Here's my part of the code which handles the adding of a field to several content types after I created the storage ($bundles holds the array of my content types):

$entityTypemanager = \Drupal::entityTypeManager();

foreach ($bundles as $bundle) {
  $field = FieldConfig::loadByName('node', $bundle, $fieldName);
  if (empty($field)) {
    FieldConfig::create(
      [
        'field_name'   => $fieldName,
        'entity_type'  => 'node',
        'bundle'       => $bundle,
        'label'        => $label,
        'required'     => TRUE,
        'translatable' => TRUE,
        'description'  => t(
          $description
        ),
      ]
    )->save();
    $message .= "\n" . t(
        "Field @a created in bundle @b.",
        ['@a' => $fieldName, '@b' => $bundle]
      );

    // Assign widget settings for the 'default' form mode.
    $displayForm = $entityTypemanager
      ->getStorage('entity_form_display')
      ->load('node.' . $bundle . '.default')
      ->setComponent($fieldName, [
        'type'   => 'options_buttons',
        'weight' => 100,
      ]);
    $messagePart = '';
    if ($displayForm->save()) {
      $messagePart .= ' form-';
    }
    else {
      $message .= "\n" . t(
          "The form display of @a in bundle @b could not be set",
          ['@a' => $fieldName, '@b' => $bundle]
        );
    }
    unset($displayForm);

    // Assign display settings for the 'default' and 'teaser' view modes.
    $displayDefault = $entityTypemanager
      ->getStorage('entity_view_display')
      ->load('node.' . $bundle . '.default')
      ->removeComponent($fieldName);
    if ($displayDefault->save()) {
      $messagePart .= ' default-';
    }
    else {
      $message .= "\n" . t(
          "The default display of @a in bundle @b could not be set",
          ['@a' => $fieldName, '@b' => $bundle]
        );
    }
    unset($displayDefault);

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
    $viewModes = \Drupal::service('entity_display.repository')
      ->getViewModes('node');
    if (isset($viewModes['teaser'])) {
      $displayTeaser = $entityTypemanager
        ->getStorage('entity_view_display')
        ->load('node.' . $bundle . '.teaser')
        ->removeComponent($fieldName);
      if ($displayTeaser->save()) {
        $messagePart .= ' teaser-';
      }
      else {
        $message .= "\n" . t(
            "The teaser display of @a in bundle @b could not be set",
            ['@a' => $fieldName, '@b' => $bundle]
          );
      }
      unset($displayTeaser);
    }
    $message .= "\n" . t(
        $messagePart . "Display(s) of @a in bundle @b are set.",
        ['@a' => $fieldName, '@b' => $bundle]
      );

  }
  else {
    $message .= "\n" . t(
        "Field @a already exists in bundle @b.",
        ['@a' => $fieldName, '@b' => $bundle]
      );
  }
}
2
  • 1
    Important to note that the uuid in the field storage config is unique to the database. You'll want to remove that so it works in other databases.
    – powpow12
    Aug 6, 2019 at 13:58
  • The question was not about YAML structure. I don´t know why he edited my post in that way. I am thinking about removing the changes but actually people see the options and it gives a direction how to export configs and use them in the code, so I´ll be lazy and let this edit go (And thx for the "Next described very well" ) :-) - just click "This answer is useful " next time. Mar 17, 2020 at 12:52
2
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;

function module_name_update_9002(){
    FieldStorageConfig::create(array(
        'field_name' => 'example_field',
        'entity_type' => 'node', 
        'type' => 'text',
        'cardinality' => -1,
        ))->save();

    FieldConfig::create([
    'field_name' => 'example_field',
    'entity_type' => 'node',
    'bundle' => 'article', // content type
    'label' => 'Example field',
    ])->save();
    // Manage form display
    $form_display = \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article' );
    $form_display = $form_display->setComponent('example_field', ['type' => 'text_textfield']);
    $form_display->save();
    // Manage view display
    $view_display = \Drupal::service('entity_display.repository')->getViewDisplay('node', 'article');
    $view_display->setComponent('example_field', ['type' => 'text_default']);
    $view_display->save();

}

Here is one example in Drupal 9, 'example_fields' is field name and and 'article' is my content type, this can be written in hook_update_N or hook_form_alter.

0

You can try this module to create fields programmatically in Drupal 8: https://drupal.org/project/field_create

Use the provided hook in your module:

function mymodule_field_create_alter(&$definitions) { }

Add your fields to the list and run the drush command:

drush field_create

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.