Skip to content

Instantly share code, notes, and snippets.

@markadrake
Created May 3, 2026 19:13
Show Gist options
  • Select an option

  • Save markadrake/ae2e151e0dc204eaa161f106d5a7e58c to your computer and use it in GitHub Desktop.

Select an option

Save markadrake/ae2e151e0dc204eaa161f106d5a7e58c to your computer and use it in GitHub Desktop.
Keep Umbraco 17 Media Center Organized

Right now the Umbraco media library dumps folders and files together in whatever order they were added. That means every time an editor goes looking for something, they're scanning a mixed-up list instead of just navigating it.

If we sort it the same way a normal folder on your computer works — folders at the top, everything alphabetical — editors instantly know where to look. It matches what they already know from Windows, Mac, Google Drive. No training needed, no confusion.

The real value compounds over time. Libraries grow, teams change, new editors join. A predictable structure means less duplication, less "where did you put that?" and less time developers spend reorganising things that shouldn't have gotten messy in the first place.

It's a small change with a disproportionate return — every single media interaction gets a little faster and a little less frustrating.

using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Notifications;
namespace Humble.Umbraco.Features.KeepMediaSorted;
public class Composer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<MediaSavedNotification, KeepMediaSortedNotificationHandler>();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
namespace Humble.Umbraco.Features.KeepMediaSorted;
public class KeepMediaSortedNotificationHandler(IMediaService mediaService, IContentTypeService contentTypeService)
: INotificationHandler<MediaSavedNotification>
{
private static readonly AsyncLocal<bool> IsSorting = new();
public void Handle(MediaSavedNotification notification)
{
if (IsSorting.Value) return;
try
{
IsSorting.Value = true;
foreach (var media in notification.SavedEntities)
{
var parent = media.ParentId > 0 ? mediaService.GetById(media.ParentId) : null;
if (parent == null) continue;
SortContainerNormal(parent);
}
}
finally
{
IsSorting.Value = false;
}
}
private void SortContainerNormal(IMedia parent)
{
var children = mediaService.GetPagedChildren(parent.Id, 0, int.MaxValue, out _)
.ToList();
if (children.Count <= 1) return;
var folders = new List<IMedia>();
var items = new List<IMedia>();
foreach (var child in children)
{
bool isFolder = IsContainerType(child.ContentType);
(isFolder ? folders : items).Add(child);
}
var sortedFolders = folders.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList();
var sortedItems = items.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList();
var sortedAll = sortedFolders.Concat(sortedItems).ToList();
mediaService.Sort(sortedAll);
}
private bool IsContainerType(ISimpleContentType contentType)
{
// 1. Built-in Folder
if (contentType.Alias == Constants.Conventions.MediaTypes.Folder)
return true;
// 2. Any custom media type that has allowed child types configured
var fullType = contentTypeService.Get(contentType.Alias);
if (fullType == null) return false;
return fullType.AllowedContentTypes?.Any() == true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment