
Product
Introducing Repository Access Permissions and Custom Roles
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.
astro-aeo-image
Advanced tools
Astro image service that embeds your alt text (and optional description/keywords) as standards XMP into the optimized output files — so your dist/_astro assets are self-describing for Google Images and AI answer engines. Wraps Astro's default shar
An Astro image service that embeds your
<Image>alt text (and optional description/keywords/license) as standards XMP/IPTC directly into the optimized output files — so yourdist/_astroassets are self-describing: Google Images reads embedded IPTC metadata and recommends embedding it, and the description travels with the file for accessibility, attribution, and the AI-search era.
Astro already requires an alt on every <Image /> — but that text normally lives only in the HTML attribute. The moment the optimized file is downloaded, hot-linked, or indexed as a file, the page context is gone. astro-aeo-image writes the description (and attribution/license) into the image bytes, where it travels with the file.
What's documented: Google Images reads embedded IPTC metadata (creator/credit/copyright/license) and recommends embedding it. For image ranking, Google uses the HTML
alt— so embedding complements it (durability, accessibility, attribution), it doesn't replace it or claim a ranking boost. AI engines consuming embedded metadata is forward-looking, not yet a spec.
image.service.config.keepMetadataThis is an authoring layer, not a preservation one — and that distinction is the whole point:
sharp's keepMetadata / keepExif | astro-aeo-image | |
|---|---|---|
| Carries through metadata already in the source file | ✅ | — |
Authors new descriptive metadata from your app data (the alt you already wrote, captions, keywords) | ❌ | ✅ |
Writes Iptc4xmpCore:AltTextAccessibility + dc:description for AEO/accessibility | ❌ | ✅ |
sharp can preserve a camera's existing EXIF; it can't compose a fresh XMP packet from the alt prop in your .astro file. That's the gap this fills.
It's a thin wrapper around Astro's own default sharp service. Everything sharp does is unchanged — same resizing, formats, quality, caching. After sharp encodes each variant, this service splices descriptive XMP into the output buffer via aeo-image: byte-preserving, no re-encode — the compressed pixels are identical; only a metadata block is added.
npm install astro-aeo-image
aeo-image comes along as its only dependency (zero-dependency itself). astro is a peer dependency (you already have it).
Add the integration — one line:
// astro.config.mjs
import { defineConfig } from "astro/config";
import aeoImage from "astro-aeo-image";
export default defineConfig({
integrations: [
aeoImage(), // or aeoImage({ useAltAsDescription: false })
],
});
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
image: {
service: {
entrypoint: "astro-aeo-image/service",
config: { useAltAsDescription: true },
},
},
});
The integration is just a thin wrapper that sets this for you and forwards options.
That's it. Every <Image /> you already have now ships its alt inside the file:
---
import { Image } from "astro:assets";
import barn from "../assets/barn.jpg";
---
<Image src={barn} alt="A weathered red barn under a violet dusk sky in rural Vermont" width={1200} height={800} />
The generated dist/_astro/barn.*.webp now contains:
Iptc4xmpCore:AltTextAccessibility = the alt textdc:description = the alt text (unless useAltAsDescription: false)Pass extra props on <Image> for fuller AEO signals (distinct SEO description, keywords, title):
<Image
src={barn}
alt="A weathered red barn at dusk"
description="A restored 1890s dairy barn in Vermont, now a working agrivoltaics site"
keywords={["barn", "vermont", "agrivoltaics", "rural"]}
title="Vermont Agrivoltaics Barn"
width={1200} height={800}
/>
| Prop | XMP field |
|---|---|
alt (required by Astro) | Iptc4xmpCore:AltTextAccessibility (+ dc:description) |
description | dc:description |
keywords (array or comma string) | dc:subject |
title | dc:title |
creator | dc:creator |
credit | photoshop:Credit |
rights | dc:rights |
copyrightNotice | photoshop:Copyright |
licenseUrl | xmpRights:WebStatement — Google Licensable |
licensor {url, name?} (or flat licensorUrl/licensorName) | IPTC PLUS plus:Licensor — Google "Get this image" link |
digitalSourceType (full IRI or bare term like "trainedAlgorithmicMedia") | Iptc4xmpExt:DigitalSourceType — AI-generated disclosure |
aiGenerated (boolean shorthand) | sets Iptc4xmpExt:DigitalSourceType to trainedAlgorithmicMedia |
ai {prompt?, promptWriter?, system?, systemVersion?} (or flat aiPrompt/aiPromptWriter/aiSystem/aiSystemVersion) | IPTC 2025.1 Iptc4xmpExt:AIPromptInformation / AIPromptWriterName / AISystemUsed / AISystemVersionUsed |
The last fields implement what Google Images reads for the Licensable badge:
<Image
src={barn}
alt="A weathered red barn at dusk"
creator="Jane Doe"
copyrightNotice="© 2026 Example Studio"
licenseUrl="https://example.com/license/barn"
licensorUrl="https://example.com/buy/barn"
width={1200} height={800}
/>
For AI-generated assets, embed the IPTC 2025.1 provenance fields plus the Digital Source Type IRI that downstream tools read to label an image as AI-generated:
<Image
src={market}
alt="A neon-lit street market at night in the rain"
aiGenerated
aiPrompt="neon street market, rain reflections, cinematic 35mm"
aiPromptWriter="Jane Doe"
aiSystem="DALL-E via Bing Image Creator"
aiSystemVersion="3"
width={1200} height={800}
/>
aiGenerated is shorthand for
digitalSourceType="trainedAlgorithmicMedia"; pass digitalSourceType
explicitly for other terms (e.g. "compositeSynthetic" — bare CV terms are
expanded to the full IPTC IRI). Per IPTC guidance, leave creator off for
fully AI-generated images — the prompt writer is explicitly not the creator.
For TypeScript autocomplete on the custom props, augment Astro's image props in src/env.d.ts:
declare namespace Astro {
interface CustomImageProps {
description?: string;
keywords?: string[] | string;
title?: string;
aiGenerated?: boolean;
aiPrompt?: string;
aiSystem?: string;
}
}
Whatever you output from Astro: WebP, AVIF, JPEG, PNG (via aeo-image). SVG/GIF and any format aeo-image doesn't handle are passed through untouched — the service degrades gracefully and never fails a build over metadata.
Metadata is written as Adobe XMP (not legacy IPTC-IIM), conforming to the IPTC Photo Metadata Standard 2025.1 (descriptive + accessibility + rights/licensing + AI-provenance subset) plus Dublin Core, Adobe, and PLUS namespaces — see aeo-image's conformance notes. This includes the 2025.1 AI-generation provenance properties and Iptc4xmpExt:DigitalSourceType (readable by exiftool, named tags from 13.40).
Given <Image alt="A weathered red barn under a violet dusk sky in rural Vermont" />, the built file gains embedded metadata:
$ exiftool dist/_astro/barn.abc123.webp | grep -iE "description|alt"
# ── before (default Astro service) ──
# (no metadata)
# ── after (astro-aeo-image) ──
Description : A weathered red barn under a violet dusk sky in rural Vermont
Alt Text Accessibility : A weathered red barn under a violet dusk sky in rural Vermont
Or check it programmatically:
node -e "import('aeo-image').then(async m=>{const {readFileSync}=await import('node:fs');console.log(m.readMetadata(new Uint8Array(readFileSync(process.argv[1]))))})" dist/_astro/<your-image>.webp
# → { description: '…', altText: '…' }
The pixels are untouched — only a metadata block is added.
aeo-image.<Image> props (including alt) to the service's transform, which is where embedding happens. For on-demand/SSR image endpoints, the custom props are included in propertiesToHash so they survive into transform; if you rely on SSR image optimization, please smoke-test and open an issue with your Astro version if a prop doesn't come through.astro/assets/services/sharp. If you use a different image service, this won't layer on top of it.MIT
FAQs
Astro image service that embeds your alt text (and optional description/keywords) as standards XMP into the optimized output files — so your dist/_astro assets are self-describing for Google Images and AI answer engines. Wraps Astro's default shar
The npm package astro-aeo-image receives a total of 242 weekly downloads. As such, astro-aeo-image popularity was classified as not popular.
We found that astro-aeo-image demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.