Migrating Drupal 7 Image Files to Drupal 8 Media Images (no PHP required!)
I’m new to migrate. And it’s a beast. But I think after a few dozen hours of diligence, I think I’ve figured out my preferred method.
This post is for someone who already understands how to use views, develop custom modules, and knows how to use drush migration operations.
I originally tried using migrate_drupal, which honestly did a great job at migrating my user roles, users, and taxonomies. But the site I am migrating is 5 years old with a good amount of technical debt having been touched by over 10 (and maybe even 20) developers over its lifetime, so I think it’s time for a fresh start. I want to migrate everything else (content types, files, etc) manually.
All the Drupal 7 content types were using image (file) fields, but I want the new Drupal 8 site to use media fields. I couldn’t use the migration provided by migrate_drupal, so I had to get creative.
This will be a two step process:
- Use D7 views_datasource module to create JSON endpoints on my old site (and since we’re using a JSON source, you can honestly migrate from anything).
- Write a custom migration in D8.
Drupal 7 and Views JSON
Create a views JSON that pulls all file content with the following fields:
- File ID (this will become the Media ID — we want to keep the IDs so when we merge content, it’ll be easier to also migrate their references)
- Name
- Absolute path (e.g. https://fullpath.com/sites/default/files/something.jpg)
- Alt text
- Date
- User ID (Many users in CMS — I want to make sure they keep ownership of their images)
- Title text (optional and not really used, but some people have taken the time to enter this field so I figure it would be worth migrating over)

Also, in the format settings, make sure you set a root object name (I used “images”) and leave the the “Top-level child object” field empty.

Now, your JSON object should look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "images": [ { "fid": "6", "filename": "your-image.jpg", "path": "https://youroldsite.com/sites/default/files/your-image.jpg", "alt": "People shaking hands", "created": "1432021142", "uid": "1", "title": "" }, { "fid": "11", "filename": "your-screenshot.jpg", "path": "https://youroldsite.com/sites/default/files/your-screenshot.jpg", "alt": "Video demo", "created": "1432021142", "uid": "1", "title": "" } ], "pager": { "pages": null, "page": null, "count": 0, "limit": 0 } } |
The Drupal 8 Migration
Now for the beast. On your new site, install the following contrib modules:
- media (core)
- migrate (core)
- migrate_plus (contrib)
- migrate_tools (contrib)
You’ll also need to apply this patch that fixes a problem with the entity_generate plugin in order to get UIDs and FIDs to import correctly into your generated file entities https://www.drupal.org/project/migrate_plus/issues/2975266
You’ll then need two files in a custom module:
- my_module/config/install/migrate_plus.migration_group.files.yml
- my_module/config/install/migrate_plus.migration.images.yml
1 2 3 4 5 6 7 8 9 10 11 12 |
# migrate_plus.migration_group.files.yml langcode: en status: true module: my_module id: files label: File imports description: Import files from my old website. source_type: JSON dependencies: enforced: module: - my_module |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# migrate_plus.migration.images.yml id: images label: Files migration_group: files migration_tags: - files source: plugin: url data_fetcher_plugin: http data_parser_plugin: json urls: - 'https://youroldsite.com/api/to/images' item_selector: images fields: - name: fid label: 'File ID' selector: fid - name: filename label: 'Name' selector: name - name: path label: 'Path' selector: path - name: alt label: 'Alt' selector: alt - name: created label: 'Created' selector: created - name: uid label: 'User ID' selector: uid - name: title label: 'Title text' selector: title ids: fid: type: integer process: # Preparation. destination_basename: plugin: callback callable: basename source: path destination_basepath: plugin: default_value default_value: public:// destination_path: plugin: concat source: - '@destination_basepath' - '@destination_basename' # Mapping. mid: fid bundle: plugin: default_value default_value: image name: name 'field_media_image/title': title 'field_media_image/alt': alt 'field_media_image/target_id': - plugin: skip_on_empty method: process source: path - plugin: file_copy source: - path - '@destination_path' - plugin: entity_generate values: uid: uid fid: fid status: plugin: default_value default_value: 1 created: created changed: created destination: plugin: entity:media |
Next Steps
Install the your custom module, and you should be good to go with a drush mim images
.
I’m going to use this to also migrate PDFs and other documents. I thought about creating one huge migration to migrate all file entities to all media entities, but I figure it would be cleaner (and more maintainable) to do one bundle at a time.
This took me longer than I’d like to admit to get together, but hopefully this works for you like it did for me. Feel free to drop a comment for any comments/questions. Good luck!