examples/functions/media-library-auto-alt-text/README.md
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. With global audiences, creating multilingual alt text becomes even more challenging, leading to accessibility gaps and poor user experience for screen reader users.
This Sanity Function automatically generates multilingual alt text for assets in your Media Library using Agent Actions. When an asset is created or updated, the function waits for Sanity's auto-generated keywords to be available, then uses Agent Actions to create concise, descriptive alt text in multiple languages that you provide. The generated alt text is stored in the asset's aspect.
This function is built to work with any Sanity project that has Media Library enabled. It operates on assets in your Media Library and doesn't require any specific Studio template.
This function uses aspects to store custom metadata on assets. Aspects are schema-style fields that apply to assets and help organize and identify them with additional metadata.
This function includes one aspect:
altText.ts - Defines the structure and editing UI for multilingual alt textImportant notes:
aspects object on each assetImportant: Run these commands from the root of your project (not inside the studio/ folder).
Initialize the example
Run this if you haven't initialized blueprints:
npx sanity blueprints init
You'll be prompted to select your organization and Sanity studio.
Then run:
npx sanity blueprints add function --example media-library-auto-alt-text
Find your Media Library ID
You'll need your Media Library ID to configure the function. Find it by going to your Media Library and check the URL https://www.sanity.io/@<organizationId>/media/<mediaLibraryId>/assets
Add configuration to your blueprint
// 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
},
},
}),
],
})
Install dependencies
Install dependencies in the project root and in the functions directory:
# Install in the functions directory
cd functions/
pnpm install
You can test the media-library-auto-alt-text function locally using the Sanity CLI before deploying it to production.
Once you've tested your function locally and are satisfied with its behavior, you can deploy it to production.
Deploy your blueprint
From your project root, run:
npx sanity blueprints deploy
This command will:
Verify deployment
After deployment, you can verify your function is active by:
When an asset is created or updated in your Media Library, the function follows this workflow:
altText in its aspects, the function exits to prevent loopsTo add or remove languages, modify the function code in functions/media-library-auto-alt-text/index.ts:
languages array with your desired language codesThe generated alt text can be edited directly in the Media Library UI:
Error: "No Media Library keywords found"
Function triggering in a loop
Generated alt text is not relevant
Asset Container vs. Image Asset:
The Media Library distinguishes between two connected entities:
Where data lives:
altText) → Asset container's aspects objectmetadata.keywords array (this is automatically done when you upload a new image).metadata objectAccessing image metadata:
To access keywords and other image data that lives on the image asset, dereference the currentVersion reference:
*[_id == $assetContainerId][0]{
...,
"metadata": currentVersion->{
metadata
}
}
This section provides technical details about how Media Library structures asset data. This is useful when building or debugging functions.
When you query an asset from the Media Library, you get a structure like this (with anonymized values):
{
"_createdAt": "2025-01-01T12:00:00Z",
"_id": "<asset-id>",
"_rev": "<revision-id>",
"_system": {
"createdBy": "<user-id>"
},
"_type": "sanity.asset",
"_updatedAt": "2025-01-02T15:00:00Z",
"aspects": {
"altText": [
{
"_key": "<key-1>",
"_type": "altTextItem",
"language": "nl",
"value": "voorbeeld alt text"
},
{
"_key": "<key-2>",
"_type": "altTextItem",
"language": "en",
"value": "Example alt text"
}
]
},
"assetType": "sanity.imageAsset",
"cdnAccessPolicy": "public",
"currentVersion": {
"_ref": "<image-asset-version-ref>",
"_type": "reference"
},
"title": "example-image.png",
"versions": [
{
"_key": "<version-key-1>",
"_type": "sanity.asset.version",
"instance": {
"_ref": "<image-asset-version-ref>",
"_type": "reference"
},
"title": "example-image.png"
}
]
}
Key Points:
aspects object contains your custom metadata (fully customizable)currentVersion field references the actual image documentversions array tracks all versions of the asset (enabling asset versioning)The actual image metadata lives on the image document, not the asset container. To access this data, dereference the currentVersion reference.
Use this GROQ query to get the full image data:
*[_id == $currentVersion._ref][0]
This returns the underlying image with detailed metadata:
{
"_id": "<image-asset-version-id>",
"_type": "sanity.imageAsset",
"metadata": {
"_type": "sanity.imageMetadata",
"blurHash": "<blur hash string>",
"dimensions": {
"_type": "sanity.imageDimensions",
"aspectRatio": 0.68,
"height": 1200,
"width": 810
},
"hasAlpha": false,
"isOpaque": true,
"keywords": ["movie poster", "character", "studio", "red cape"],
"lqip": "<data-url>",
"palette": {
"_type": "sanity.imagePalette",
"darkMuted": {
"_type": "sanity.imagePaletteSwatch",
"background": "#4c3134",
"foreground": "#fff",
"population": 0.36,
"title": "#fff"
}
}
},
"originalFilename": "<image-file-name>.jpg",
"mimeType": "image/jpeg",
"cdnAccessPolicy": "public"
}
Important Notes:
metadata.keywords on the image assetcurrentVersion for accuracy