Update Drupal Field with Drush Commands

What is the Problem?

Sometimes one needs to change existing nodes on a Drupal 9 or Drupal 10 website. If you have 100’s or 1000’s of existing nodes, this can be a daunting task. Imagine doing this manually. One would have to edit each node, save the node, check your work, and then go on to the next one. This does not sound fun.

Overview of the Solution for Changing Multiple Nodes

We will set up a custom Drupal module that will hold drush commands that you can run on your command line to update your database. Drush gives you the ability to write shorthand for your commands and set whatever arguments you need to run your functions.

Set up a custom Drupal module

You will need to set up these files to store your code:

update_field.info.yml
update_field.services.yml
src/Commands/DrushFields.php

Place these in a folder in modules/custom called update_field. Or you can name your module whatever you want, just be sure to substitute update_field with your new name.

Code for Your Update Field Files

This is the code for update_field.info.yml:

name: "Update Field"
type: module
description: "Use Drush Commands to Update a Field."
core_version_requirement: ^9 || ^10
package: "custom"

This is the code for update_field.services.yml:

services: 
  update_field.drush_command:
    class: \Drupal\update_field\Commands\DrushFields
    tags:
      - { name: drush.command }

Yaml (yml) files are fussy about spacing, so carefully type each space. If you have issues, try an online yaml validator. You can learn more about yml files in Drupal by visiting the Drupal yml page.

Write the Function for What You Need to Solve

Before you write up the drush commands file, focus on the function that you need to solve your problem. Do you have a specific string that you want to insert in this field? Do you want to change the text from one string to another? Do you want to set the value to the default value?

In this post, the code will set the text of a specific field to a hard-coded value. This is the function to update the field:

use Drupal\Core\Entity\EntityTypeManagerInterface;

function processNode($nid, $newtext, $field_name){
    $entity_manager = \Drupal::entityTypeManager();
    $node = $entity_manager->getStorage('node')->load($nid);
    if ($node instanceof \Drupal\node\NodeInterface) {
      $node->get($field_name)->setValue(['value' => $newtext]);
      $node->save();
    }
  }

The function takes a node id ($nid), a string of text ($newtext), and a field name ($field_name) as parameters. It sets the value of $field_name to $newtext in a particular node. Then the node is saved.

You might want to try writing a similar function inside a hook_ENTITY_TYPE_presave, and try it out in update_field.module. When using a hook, one substitutes the hook part of the name of the function with the name of your module. Maybe I will write more about hooks in another post.

Your needs might be slightly different. Maybe you only want to update one content type, or maybe you want to set the field to the default value. You can customize the drush commands for the inputs that you need in your function.

Add the Drush Commands

Now let’s write up the drush commands file. One of the nice features of drush is that you can set it up as you want. We use the annotated method for commands before the class function:

/**
   * Update Field.
   *
   * @param string $newtext
   *   New text for your field.
   *   Argument provided to the drush command.
   *
   * @param string $type
   *   Type of node to update
   *   Argument provided to the drush command.
   * 
   * @command update:field
   * @aliases uf
   *
   * @usage update:field 'my new text' content_type 
   *   'my new text' is the $newtext value
   *   content_type is the machine name of type to update
   */

The parameters for the drush commands are $newtext and $type. If your function has different requirements, you can alter these.

The alias here is ‘uf’ — you can make it whatever simple command string you want.

See the last part that says @usage to see how you will use this on the command line. You will type `drush uf ‘my new text’ article` to update your field in article with ‘my new text’.

Note as of June 2024: one can now use PHP 8 Attributes instead of annotations. Learn more in this post on Custom Drush commands by Four Kitchens.

Create extends DrushCommands Class

Let’s put the drush commands that we created above into a class. Inside DrushFields.php, add this code:

public function updateField($newtext, $type) {
    $field_name = "field_summary";
    $entity_manager = \Drupal::entityTypeManager();
    $batch_size = 10;

    // Get a list of node IDs to process.
    $query = $entity_manager->getStorage('node')->getQuery()
      ->condition('type', $type);
    $nids = $query->execute();

    if (!empty($nids)) {

      $batch = new BatchBuilder();

      foreach ($nids as $nid) {
          $batch->addOperation([DrushFields::class, 'processNode'], [$nid, $newtext, $field_name]);
      }

      batch_set($batch->toArray());
      drush_backend_batch_process();
    }
  }

The code takes the new text and the content type on the command line. It processes the nodes in batches to help with memory issues. You can set the $batch_size to any amount that works for your system.

To the top of the DrushFields.php file, add the namespace and the necessary use statements:

namespace Drupal\update_field\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Commands\DrushCommands;
use Drupal\Core\Batch\BatchBuilder;

Under those one adds the class declaration:

/**
 * A Drush commandfile.
 */
class DrushFields extends DrushCommands {

Outputs Along the Way

When you run the drush commands, it’s nice when you get responses for what is happening in the system. You can use
$this->output()->writeln("your text")
in your code to get outputs that you want.

Here is the updateField function once again, with outputs:

 public function updateField($newtext, $type) {
    $field_name = "field_summary";
    $this->output()->writeln('New Field Value: '. $newtext."  ");
    $this->output()->writeln('Content Type: '. $type."\n");
    $entity_manager = \Drupal::entityTypeManager();
    $batch_size = 10;

    // Get a list of node IDs to process.
    $query = $entity_manager->getStorage('node')->getQuery()
      ->condition('type', $type);
    $nids = $query->execute();

    if (!empty($nids)) {

      $this->output()->writeln("Number of nodes: ". count($nids));
      $batch = new BatchBuilder();

      foreach ($nids as $nid) {
          $batch->addOperation([DrushFields::class, 'processNode'], [$nid, $newtext, $field_name]);
      }

      batch_set($batch->toArray());
      $this->output()->writeln("Start the batch process.");
      drush_backend_batch_process();

      $this->output()->writeln("\nBatch processing completed.");
    }
    else {
      $this->output()->writeln("No nodes of type $type found.");
    }
  }

Final DrushFields.php

Here’s how your DrushFields.php should look:

namespace Drupal\update_field\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Commands\DrushCommands;
use Drupal\Core\Batch\BatchBuilder;

/**
 * A Drush commandfile.
 */
class DrushFields extends DrushCommands {

  /**
   * Update Field.
   *
   * @param string $newtext
   *   New text for your field.
   *   Argument provided to the drush command.
   *
   * @param string $type
   *   Type of node to update
   *   Argument provided to the drush command.
   * 
   * @command update:field
   * @aliases uf
   *
   * @usage update:field 'my new text' content_type 
   *   'my new text' is the $newtext value
   *   content_type is the machine name of type to update
   */
  public function updateField($newtext, $type) {
    $field_name = "field_summary";
    $this->output()->writeln('New Field Value: '. $newtext."  ");
    $this->output()->writeln('Content Type: '. $type."\n");
    $entity_manager = \Drupal::entityTypeManager();
    $batch_size = 10;

    // Get a list of node IDs to process.
    $query = $entity_manager->getStorage('node')->getQuery()
      ->condition('type', $type);
    $nids = $query->execute();

    if (!empty($nids)) {

      $this->output()->writeln("Number of nodes: ". count($nids));
      $batch = new BatchBuilder();

      foreach ($nids as $nid) {
          $batch->addOperation([DrushFields::class, 'processNode'], [$nid, $newtext, $field_name]);
      }

      batch_set($batch->toArray());
      $this->output()->writeln("Start the batch process.");
      drush_backend_batch_process();

      $this->output()->writeln("\nBatch processing completed.");
    }
    else {
      $this->output()->writeln("No nodes of type $type found.");
    }
  }

  public static function processNode($nid, $newtext, $field_name){
    $entity_manager = \Drupal::entityTypeManager();
    $node = $entity_manager->getStorage('node')->load($nid);
    if ($node instanceof \Drupal\node\NodeInterface) {
      $node->get($field_name)->setValue(['value' => $newtext]);
      $node->save();
    }
  }
}

Add a README.md File

Finally, I recommend setting up a README.md to store the information about how to run the drush commands. Someone else might want to use your code. Or you may come back in a year and need a quick refresh on how to run the drush commands.

More on Drupal and Drush Commands