Automating Image Alt Text Generation with AI

Introduction

Alternative text (alt text) is critical for both accessibility and SEO. Screen readers rely on it to describe images to visually impaired users, and search engines use it to better understand the content of a page.
In our Optimizely CMS project, we implemented an automated “generate alt text” task that scans the site for images, sends them to an AI service for description, and writes the results back into content properties.

The first version of this task manually traversed content and guessed which images were used where. We later improved it by using Optimizely’s built‑in IContentModelUsage API to reliably discover all usages of a given image. This post documents the final approach.


Scenario Overview

The goal of the task was:

  • Find all image assets in the media library that are missing good alt text.
  • For each image, find all content items (pages/blocks) that actually use it.
  • Generate a descriptive alt text string using an AI service.
  • Update the corresponding alt text fields on the content models.
  • Run everything as a scheduled job so editors can trigger it on demand.

The key improvement was replacing the fragile “get used images” logic with IContentModelUsage, which understands how media is referenced across the site at the data model level.


Step 1: Discover Images Missing Alt Text

We start by querying media assets and filtering those that do not have meaningful alt text. This depends on your media model, but a typical pattern looks like this:

public class ImageFile : ImageData
{
    [Display(Name = "Alt text", Order = 100)]
    public virtual string AltText { get; set; }
}

In the scheduled job:

var imagesWithoutAlt = _contentRepository
    .GetDescendents(ContentReference.GlobalBlockFolder) // or your media root
    .Select(x => _contentRepository.Get<ImageFile>(x))
    .Where(img => string.IsNullOrWhiteSpace(img.AltText))
    .ToList();

This gives us the set of candidate images to enrich.


Step 2: Get Used Images with ContentModelUsage

Originally, the job tried to infer where images were used by scanning content properties and matching IDs. That was brittle and easy to break when new content types or properties were added.

The improved version uses IContentModelUsage:

private readonly IContentModelUsage _contentModelUsage;

public AltTextGenerationJob(
    IContentRepository contentRepository,
    IContentModelUsage contentModelUsage,
    IAltTextService altTextService,
    ILogger logger)
{
    _contentRepository = contentRepository;
    _contentModelUsage = contentModelUsage;
    _altTextService = altTextService;
    _logger = logger;
}

private IEnumerable<UsageInContentModel> GetImageUsages(ContentReference imageRef)
{
    // This asks Optimizely where a specific asset is referenced
    return _contentModelUsage.ListContentOfContent(imageRef);
}

Each UsageInContentModel tells us which content item uses the image, and in many cases which property holds the reference. This is more robust than hand‑rolled reflection because it respects how Optimizely stores references internally.


Step 3: Generate Alt Text with AI

For each image and its usages, we call an AI service that can inspect the image and return a natural‑language description. The implementation details depend on your provider (Azure OpenAI, custom vision, etc.), but the pattern is similar:

public interface IAltTextService
{
    Task<string> GenerateAltTextAsync(ImageFile image);
}

public async Task<string> GenerateAltTextAsync(ImageFile image)
{
    var url = _urlResolver.GetUrl(image.ContentLink);
    // Call external AI API with the image URL
    var description = await _aiClient.DescribeImageAsync(url);
    return description.Truncate(200); // keep alt text reasonably short
}

The scheduled job coordinates the flow:

foreach (var image in imagesWithoutAlt)
{
    var usages = GetImageUsages(image.ContentLink).ToList();
    if (!usages.Any())
    {
        _logger.Information("Image {ImageId} is not used anywhere, skipping.", image.ContentLink);
        continue;
    }

    var altText = await _altTextService.GenerateAltTextAsync(image);
    if (string.IsNullOrWhiteSpace(altText))
    {
        _logger.Warning("AI did not return alt text for image {ImageId}", image.ContentLink);
        continue;
    }

    var writable = image.CreateWritableClone() as ImageFile;
    writable.AltText = altText;
    _contentRepository.Save(writable, SaveAction.Publish, AccessLevel.NoAccess);

    _logger.Information(
        "Generated alt text for image {ImageId} used in {UsageCount} items.",
        image.ContentLink, usages.Count);
}

Here GetImageUsages is powered by IContentModelUsage, so we know exactly how widely each image is used and can prioritise accordingly (for example, skip unused assets or log them separately).


Step 4: Benefits of Using ContentModelUsage

Switching the “get used images” step to IContentModelUsage delivered several concrete benefits:

  • Accuracy: We no longer miss usages that are hidden behind complex page/block structures; Optimizely’s own model usage API tracks references reliably.
  • Refactor safety: When editors or developers introduce new content types or image properties, IContentModelUsage still works without any code changes in the scheduled job.
  • Better reporting: The usage list makes it easy to log how often an image is used, helping editors prioritise which images to keep or replace.
  • Cleaner code: The job code focuses on the alt‑text workflow instead of low‑level reflection and property scanning.

Overall, the combination of ImageFile.AltTextIContentModelUsage, and an AI description service gave us a repeatable way to improve accessibility across thousands of images with minimal manual effort.


Conclusion

Automated alt text generation is a powerful way to raise the accessibility baseline of an Optimizely CMS site. The key design choice in this project was to let the platform itself tell us where images are used, via IContentModelUsage, instead of trying to guess.

← Quay lại Blog