npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

astro-aeo-image

v0.4.0

Published

Astro image service that embeds your <Image> 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

Readme

astro-aeo-image

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 your dist/_astro assets 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.

npm license

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.

How it's different from image.service.config.keepMetadata

This 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.

How it works

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.

Install

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).

Configure

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 text
  • dc:description = the alt text (unless useAltAsDescription: false)

Optional richer metadata

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:WebStatementGoogle Licensable | | licensor {url, name?} (or flat licensorUrl/licensorName) | IPTC PLUS plus:LicensorGoogle "Get this image" link | | digitalSourceType (full IRI or bare term like "trainedAlgorithmicMedia") | Iptc4xmpExt:DigitalSourceTypeAI-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 |

Make images Licensable in Google

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}
/>

Label AI-generated images (IPTC 2025.1)

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;
  }
}

Supported formats

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.

Standards

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).

Verifying it worked

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.

Status & scope

  • The metadata-embedding core is unit-tested against the published aeo-image.
  • On static builds, Astro passes <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.
  • Wraps astro/assets/services/sharp. If you use a different image service, this won't layer on top of it.

License

MIT