Auto-generate alt text for Media Library assets
Automatically generate accessible, multilingual alt text for images in your Sanity Media Library using Agent Actions and Sanity Functions
By Bram Doppen
studio/aspects/altText.ts
import {defineAssetAspect, defineField, defineArrayMember} from 'sanity'
const languages = [
{title: 'Dutch', value: 'nl'},
{title: 'English', value: 'en'},
{title: 'French', value: 'fr'},
{title: 'German', value: 'de'},
]
export default defineAssetAspect({
name: 'altText',
title: 'Alternative text',
description: 'Accessible alternative text for this asset, in one or more languages.',
type: 'array',
of: [
defineArrayMember({
name: 'altTextItem',
type: 'object',
fields: [
defineField({
name: 'language',
type: 'string',
description: 'The language that the alt text is written in',
options: {
list: languages,
layout: 'radio',
},
}),
defineField({
name: 'value',
title: 'Alternative text',
type: 'string',
description: 'Short description of the image, for screen readers (max ~100 characters).',
}),
],
preview: {
select: {
title: 'value',
subtitle: 'language',
},
},
}),
],
})index.ts
import {documentEventHandler} from '@sanity/functions'
import {createClient} from '@sanity/client'
const MAX_KEYWORD_WAIT = 5 // How many times to retry (total attempts = MAX_KEYWORD_WAIT + 1)
const KEYWORD_WAIT_MS = 1500 // Wait 1.5 seconds between checks
const languages = ['nl', 'en', 'fr', 'de']
// We have to wait for the keywords to be available because they are auto-generated by the Media Library when an image is uploaded.
async function waitForKeywords(
fetchKeywords: () => Promise<string[] | undefined>,
): Promise<string[] | undefined> {
for (let i = 0; i <= MAX_KEYWORD_WAIT; i++) {
console.log('Waiting for keywords...', i)
const keywords = await fetchKeywords()
if (keywords && keywords.length > 0) {
return keywords
}
if (i < MAX_KEYWORD_WAIT) {
await new Promise((res) => setTimeout(res, KEYWORD_WAIT_MS))
}
}
return undefined
}
export const handler = documentEventHandler(async ({context, event}) => {
const mlId = context.eventResourceId
const {_id, currentVersion} = event.data
const detailedAssetId = currentVersion?._ref
if (!detailedAssetId) {
console.log('No detailedAssetId found, skipping')
return
}
// Create a Media Library client
const mediaLibraryClient = createClient({
token: context.clientOptions.token,
useCdn: false,
apiVersion: '2025-05-08',
resource: {
type: 'media-library',
id: mlId,
},
})
// Query keywords from the Media Library asset using client.fetch()
const fetchKeywords = async () => {
try {
const result = await mediaLibraryClient.fetch<{keywords?: string[]}>(
`*[_id == $assetId][0]{ "keywords": metadata.keywords }`,
{assetId: detailedAssetId},
)
return result?.keywords || []
} catch (err) {
console.error('Failed fetching keywords from asset', err)
return []
}
}
const keywords = await waitForKeywords(fetchKeywords)
if (!keywords || keywords.length === 0) {
console.log('No keywords found after retries, skipping')
return
}
// Generate alt text based on the keywords using Agent Actions
const agentClient = createClient({
...context.clientOptions,
dataset: 'production',
apiVersion: 'vX',
})
// Generate alt text for each language separately for reliability
const altTextItemsArray: {_key: string; _type: string; language: string; value: string}[] = []
for (const lang of languages) {
const altText = await agentClient.agent.action.prompt({
instruction: `Given the following keywords: [${keywords.join(', ')}], generate a short (max 100 chars) alt text in language: ${lang}. Respond with just the alt text string, no quotes or formatting.`,
})
altTextItemsArray.push({
_key: crypto.randomUUID(),
_type: 'altTextItem',
language: lang,
value: String(altText).trim(),
})
}
// Update the asset with the alt text using the Media Library client
const result = await mediaLibraryClient
.patch(_id)
.setIfMissing({aspects: {}})
.set({'aspects.altText': altTextItemsArray})
.commit()
console.log('Mutation response:', JSON.stringify(result, null, 2))
})sanity.blueprint.ts
import {defineBlueprint, defineMediaLibraryAssetFunction} from '@sanity/blueprints'
export default defineBlueprint({
resources: [
defineMediaLibraryAssetFunction({
name: 'media-library-auto-alt-text',
memory: 2,
timeout: 30,
src: './functions/media-library-auto-alt-text',
event: {
on: ['create', 'update'],
filter: 'assetType == "sanity.imageAsset" && !defined(aspects.altText)',
projection: '{ _id, currentVersion }',
resource: {
type: 'media-library',
id: '<your-media-library-id>', // TODO: replace with your media library id
},
},
}),
],
})The Problem: Content teams need to provide accessible alt text for images across multiple languages, but manually writing alt text for every asset is time-consuming and often inconsistent. As the asset count grows and audiences become more global, creating and maintaining high-quality multilingual alt text becomes a bottleneck, leading to accessibility gaps and a poorer experience for screen reader users.
The Solution: This function automatically generates concise, descriptive alt text for images in your Media Library using Sanity’s Agent Actions. When an asset is created or updated, the function waits for Sanity’s auto-generated image keywords, then produces multilingual alt text and stores it directly on the asset as an aspect. Editors get accessible, editable alt text by default without manual effort.
Quick Start
View the complete example and source code.
Initialize blueprints if you haven't already:npx sanity blueprints init
Add the function to your project:npx sanity blueprints add function --example media-library-auto-alt-text
Deploy to production:npx sanity blueprints deploy
How It Works
When an image asset is created or updated in the Media Library, the function automatically:
- Triggers on asset create and update events in the Media Library
- Checks whether alt text already exists to prevent update loops
- Waits for Sanity’s machine-generated image keywords to become available (with retry logic)
- Uses Sanity’s AI-powered Agent Actions to generate concise alt text, one language at a time
- Stores the generated multilingual alt text in the asset’s aspects, where it can be edited in the Media Library UI
Key Benefits
- Accessibility by default—every image gets descriptive alt text automatically
- Multilingual support—generate alt text in multiple languages at once
- Customizable prompts to match brand voice and accessibility guidelines
- Editable results that content teams can review and refine
- Significant time savings by eliminating manual alt text creation
Technical Implementation
The function uses Sanity’s Media Library Asset Functions combined with AI Agent Actions. It reads machine-generated image keywords from the underlying image asset, generates alt text per language, and writes the results to a custom Media Library aspect attached to the asset container.
Perfect For
- Teams managing large, shared Media Libraries
- Organizations with multilingual or international audiences
- Accessibility-focused content teams
- Projects that want automated metadata without changing editorial workflows
The function is compatible with any Sanity project that has Media Library enabled and can be easily customized to add or remove languages, adjust prompts, or integrate review and approval workflows.
Contributor

Bram Doppen
Senior Solution Architect @ Sanity
Netherlands