How to convert existing image fields into Media images

Converting existing image fields to Media image fields is easier than you might think. I just converted all image fields to Media image fields on this website and in this article, I'll show you how I did it.

I created this website about four years ago, and at the time Media wasn't in the core. I used regular image fields to store images in my content types and paragraphs.

While there is nothing wrong with regular image fields, using Media is much better at least from the content editing perspective. UI looks much better and you can re-use existing images, and that isn't the case with image fields.

I mean look at the Media library modal window. Isn't that cool? You see all your existing Media images, and you can re-use them.

Media library
Media library

So, here is how I migrated from regular images to Media images.

1. Enable modules

The very first step you have to do is to enable Media and Media Library modules. These modules are not enabled by default. Once enabled, you'll see several Media types. In this case, we care only about the Image media type, so if you don't need them you can delete other media types.

2. Create media image fields

For each image field create a media image counterpart. For example, my Article content type has the Social share image field, so I created the Social share image media field.

Media image fields
Media image fields

As you can see in the screenshot, Social share image media is an entity reference field. And the settings for this field look like this:

Media image field settings
Media image field settings

3. Create and attach Media entities

Now, comes the fun part. We get to write some code.

Image field holds the reference to file entities. We just need to create Media entities and attach those existing file entities. This is how I did it. 

function gn_core_update_8001(&$sandbox) {
  $entity_types = ['node', 'paragraph'];
  $image_fields = ['field_image', 'field_social_share_image'];

  foreach ($entity_types as $entity_type) {
    $entities = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->loadMultiple();

    foreach ($image_fields as $image_field) {
      gn_core_migrate_to_media($entities, $image_field);
    }
  }
}

function gn_core_migrate_to_media($entities, $image_field) {
  $image_media_field_name = $image_field . '_media';

  foreach ($entities as $entity) {
    if (
      $entity->hasField($image_field) &&
      $entity->get($image_field)->isEmpty() === FALSE &&
      $entity->hasField($image_media_field_name)
    ) {
      $images = $entity->get($image_field)->getValue();
      $entity->set($image_media_field_name, NULL);

      foreach ($images as $image) {
        $media = \Drupal::entityTypeManager()
          ->getStorage('media')
          ->create([
            'bundle' => 'image',
            'uid' => 1,
          ]);
        $media->set('field_media_image', $image);
        $media->save();
        $entity->get($image_media_field_name)->appendItem($media);
      }

      $entity->save();
    }
  }
}

The code is pretty much self-explanatory. I'm looping through all node and paragraph entities, checking if image fields called field_image and field_social_share_image exist and they aren't empty and then creating a Media entity with a reference to existing file entities. The last thing is to attach those Media entities to nodes and paragraphs.

I added this code to an update hook in my custom GN Core module, but you can also use something like Devel execute PHP if you don't want to use update hooks.

4. Final adjustments

The last step is to check all your custom Twig templates and Sass/CSS files and adjust them if needed. Also, if you are using images as tokens you need to update those as well. In my case, I'm using the Social share image media field for the Open Graph image tag.

Media image token
Media image token

So, I had to update the token for that field from this:

[node:field_social_share_image:entity:url]

 to this:

[node:field_social_share_image_media:entity:field_media_image:entity:url]

After you are completely sure that everything works fine, you can delete your old image fields.

And that's it. I told you it isn't that complicated.

NOTE: I have less than 200 images, so I converted everything in one request. If you have more than that, consider using Batch API or Queue API as Nate Andersen pointed out in his comment on LinkedIn