Migrating your Umbraco 4 media to Umbraco 7

Exporting and importing media between different versions of Umbraco

Recently, we here at Etch had an interesting project requirement. Our client wanted to migrate all of their images and documents from one instance of Umbraco to another. Well, we figured it out, and we wanted to share! So here's how to export your media library from Umbraco 4, and import it into Umbraco 7.

You may have come across the CMS Import package, that allows you to migrate content and pull with it any referenced media. This is slightly different to what we're doing here which is to migrate an entire folder from the media library.

There are two steps in the process:

Step 1. In your Umbraco 4 site, create a Base method to export media items to JSON including a URL to download each media item

Step 2. In your Umbraco 7 site, create an API endpoint to which you will post the output from step 1. This endpoint will loop through each item, download the file and create the media item in the new site

Before: Umbraco 4 media library

After: Umbraco 7 media library

Step 1. Exporting Umbraco 4 media to JSON

We will create an Umbraco Base method to do this, as this is the documented way to create API endpoints in Umbraco 4.

Here's the main section of code (see the full code):

[RestExtensionMethod(ReturnXml = false)]
public static string Export()
	var context = HttpContext.Current;

	// must specify a folderId in the querystring, e.g. ?folderId=1234
	var folderId = context.Request.QueryString["folderId"];
	if (string.IsNullOrWhiteSpace(folderId))
		context.Response.StatusCode = 400;
		return "Must specify folder ID";

	// load the root media folder
	var root = uQuery.GetMedia(folderId);
	if (root == null)
		context.Response.StatusCode = 404;
		return "Folder not found";

	// the base url to use for making an absolute URL from each media item's url path
	var baseMediaUrl = context.Request.Url.GetLeftPart(UriPartial.Authority);

	// get all media items within the given folder, convert to a simple DTO and sort by level so that folders can be created before their child items
	var dto = root.GetDescendantOrSelfMedia().Select(m => ToDTO(m, baseMediaUrl)).OrderBy(m => m.Level);

	context.Response.ContentType = "application/json";
	return JsonConvert.SerializeObject(dto);

The key line is this one:

root.GetDescendantOrSelfMedia().Select(m => ToDTO(m, baseMediaUrl)).OrderBy(m => m.Level);

We're using uQuery to generate a flat list of all descendant media of the specified folder. We convert each of these to a simple DTO and sort by level so that when we process these items, parent folders can be created before their child items.

Here is an example of what we would now expect when we hit this endpoint:

In this example, we are requesting the entire media library by passing folderId=-1, rather than scoping it to a more specific folder. Also, in reality you will likely have a lot more media than in the example here.

Step 2. Importing media to Umbraco 7

The next step is writing an import script that can take this JSON, and use it to create media items in our Umbraco 7 site. We will do this by creating an UmbracoApiController in our Umbraco 7 site that can receive the JSON within a POST request. You'll need to copy the JSON output from Step 1, and paste it into the request body when making the request to import the media.

Here is the Import action from our controller (see the full code):

public string Import([FromBody] IEnumerable<MediaDTO> media)
	Logger.Info(typeof(MediaToolsController), "Starting media import");

	// use the Umbraco MediaService API
	var mediaService = Services.MediaService;
	var imported = new Dictionary<int, int>();

	// order by level to ensure parent folders are created before their children are imported
	media = media.OrderBy(m => m.Level);

	var count = 0;
	foreach (var item in media)
		Logger.Debug(typeof(MediaToolsController), $"Processing item {item.Id}");

		if (imported.ContainsKey(item.Id))
			Logger.Debug(typeof(MediaToolsController), "Skipping item as it is already imported");

		IMedia mediaItem;
			using (var client = new WebClient())
				// if we've already imported the parent item, find the ID of the newly created media item, otherwise use the root media folder (-1) as the parentId
				var parentId = imported.ContainsKey(item.ParentId) ? imported[item.ParentId] : -1;
				// create the new media item
				mediaItem = mediaService.CreateMedia(item.Name, parentId, item.Type);

				// if there's a file associated with this media item
				if (!string.IsNullOrWhiteSpace(item.Url))
					var fileName = Path.GetFileName(new Uri(item.Url).LocalPath);
					var fileData = client.DownloadData(item.Url);
					Logger.Debug(typeof(MediaToolsController), $"Downloaded file data ({item.Url})");
					var uploadFile = new MemoryStream(fileData);

					// put the file contents to the media item
					mediaItem.SetValue(Constants.Conventions.Media.File, fileName, uploadFile);

				Logger.Debug(typeof(MediaToolsController), $"Saved media item (new media ID {mediaItem.Id})");
		catch (Exception ex)
			Logger.Warn(typeof(MediaToolsController), $"Failed to download and import item {item.Id} ({ex.Message})");

		// record the ID of the new media item against the old media item ID
		imported[item.Id] = mediaItem.Id;

	Logger.Info(typeof(MediaToolsController), $"Imported {count} media items");

	return $"Imported {count} media items";

In order to hit this endpoint, you will need to use PostMan or some other tool for making requests. Here's an example of the request you'll need to make:

POST /Umbraco/Api/MediaTools/Import

Plus you need to send the JSON that we exported from the Umbraco 4 site as the body of this request.

PostMan request to import the media

That's it! You should now see that the files have been downloaded from the old site, and media items created on the new site.

View the full code

In the full code, we have implemented a couple of additional features for completeness;

  • Membership authorization for the Base method on the Umbraco 4 site.
  • Umbraco backoffice authentication for the WebAPI method in the Umbraco 7 site. This means that you will need to use PostMan Interceptor to make requests to your endpoint by copying the authentication cookie from another request to the Umbraco backoffice.
  • When importing media, we save the list of imported media items to a file so that we can pick up where we left off if the import request fails or times out.

About Mike Chart

As lead developer at Etch, Mike is in charge of the technical delivery of the Digital Products that Etch make. He has had a love for programming from a young age, and enjoys developing in ASP.Net, Umbraco and AngularJS. He plays ultimate frisbee and board games, but not at the same time.