Testimonial

"CMSImport, every Umbraco content migrators best friend"

Ismail Mayat, The Cogworks

 

Handle Missing Media Yourself with MediaFile notifications in CMSImport

July 03, 2026

When you import content into Umbraco that references media files, CMSImport needs those files to exist on disk before it can create the corresponding media items. But what happens when a referenced file isn’t there yet — for example, when you’re migrating from a live website and the media only exists at a remote URL?

Until now, that was a dead end unless CMSImport handled the download for you. We’ve deliberately never built automatic remote-download-and-import into the tool itself: fetching arbitrary files from a URL and importing them as your own media raises real copyright and licensing questions that we don’t want to make on your behalf. Only you know whether you have the rights to pull that image, PDF, or video from where it lives and republish it as part of your own site.

That’s why CMSImport now gives you the hook to do it yourself.

MediaFileImportingNotification

This new notification fires right before CMSImport imports a media file, and tells you whether the file already exists on disk via FileExistsOnDisk. If it doesn’t, you get the relative URL and the target file path — everything you need to fetch or generate the file yourself, under your own rules and your own responsibility.

A minimal example that downloads the missing file from your live site before the import continues:

public class DownloadMediaNotificationHandler(IOptions<CmsImportConfig> config)
    : INotificationAsyncHandler<MediaFileImportingNotification>
{
    private readonly CmsImportConfig _config = config.Value;

    public async Task HandleAsync(MediaFileImportingNotification notification, CancellationToken cancellationToken)
    {
        try
        {
            if (!notification.FileExistsOnDisk)
            {
                //Get the media path
                var downloadUrl = $"https://www.mywebsite.com{notification.RelativeUrl}";
                //download so we can import the file
                await DownloadFileAsync(downloadUrl, notification.FilePath);
            }
        }
        catch (Exception ex)
        {
        }
    }

    private async Task DownloadFileAsync(string fileUrl, string destinationFileName)
    {
        using var httpClient = new HttpClient();

        Directory.CreateDirectory(Path.GetDirectoryName(destinationFileName));

        var response = await httpClient.GetAsync(fileUrl);
        response.EnsureSuccessStatusCode();

        var fileBytes = await response.Content.ReadAsByteArrayAsync();
        await File.WriteAllBytesAsync(destinationFileName, fileBytes);
    }
}

Register it in your composer just like any other CMSImport notification handler, and every missing file will be resolved before the import moves on — no more failed imports because a media file wasn’t pre-staged on disk.

MediaFileImportedNotification

Once a media item has been created, MediaFileImportedNotification gives you a chance to enrich it before it’s finalized. A common use case: setting the correct creation date, for example when migrating content and you want the media library to reflect when the asset was originally created, not when it was imported.

public class SetCreateDateHandlerNotificationHandler(IMediaService _mediaService, IRefererenceService _referenceService)
    : INotificationAsyncHandler<MediaFileImportedNotification>
{
    public async Task HandleAsync(MediaFileImportedNotification notification, CancellationToken cancellationToken)
    {
        var createDate = await _referenceService.GetCreateDate(notification.RelativeUrl);

        notification.MediaItem.CreateDate = createDate;

        _mediaService.Save(notification.MediaItem);
    }
}

(IRefererenceService here is just a stand-in for whatever lookup service you use to determine the correct date — swap it for your own logic.)

Why this approach

We could have baked “just download it” into CMSImport by default. We chose not to, because that decision — whether you’re allowed to copy a piece of media from another location — isn’t ours to make for you. By exposing MediaFileImportingNotification, you stay fully in control: download from your own CDN, pull from a DAM you’re licensed to use, generate a placeholder, or skip the file entirely. CMSImport just gives you the moment to decide.

Both notifications are available now and documented in the CMSImport manual under Notifications.