
Product
Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection
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.
Write descriptive + rights metadata (captions, keywords, alt text, creator, license) into WebP, AVIF, HEIC, JPEG & PNG — self-describing images that Google Images reads and recommends embedding, built for the AI-search era. The only pure-JS, zero-dependen
Write descriptive + rights metadata — captions, keywords, alt text, creator, license — into WebP, AVIF, HEIC, JPEG & PNG so your images are self-describing: Google Images reads embedded IPTC metadata (and recommends embedding it), and the description travels with the file as images get downloaded, indexed, and ingested by AI pipelines. The only pure-JS, zero-dependency library that writes XMP to AVIF/HEIC. Byte-preserving (never re-encodes). Runs on Node, Bun, Deno & edge.
import { writeMetadata, readMetadata } from "aeo-image";
const tagged = writeMetadata(webpBytes, {
description: "A golden retriever catching a frisbee on a beach at sunset",
keywords: ["dog", "beach", "sunset"],
altText: "Brown dog mid-jump catching an orange frisbee",
});
readMetadata(tagged);
// → { description: "...", keywords: ["dog","beach","sunset"], altText: "..." }
The image pixels are never re-encoded. Only the metadata block is spliced in.
Metadata embedded inside an image file travels with it — when the file is downloaded, hot-linked, indexed by image search, or ingested by an AI pipeline as a file, the page's HTML context is gone but the embedded description, attribution, and license remain. That metadata lives in XMP (and IPTC), a packet inside the container. See What Google actually documents below for the evidence-backed specifics.
Today, writing XMP into modern web image formats from JavaScript means one of:
| Option | Problem |
|---|---|
exiftool (Perl) / wrappers | Requires a binary; won't run in a sandboxed cloud function |
sharp (libvips) | Native dependency and re-encodes your pixels — quality loss; cannot even write XMP to AVIF |
piexifjs | JPEG-first; WebP write is buggy; no AVIF |
exifr / ExifReader | Read-only |
No other pure-JS, zero-dependency library writes descriptive metadata into WebP and AVIF without re-encoding — not even sharp can write XMP to AVIF. aeo-image does. See docs/landscape.md for the full competitive analysis.
Uint8Array/DataView.description/keywords/altText, not raw tag IDs. We map them onto the correct XMP namespaces (dc:, photoshop:, Iptc4xmpCore:, Iptc4xmpExt:, xmpRights:).fs required; operates on buffers.removeMetadata() strips XMP/EXIF in one call (keeps ICC colour profile).require()-able on Node ≥ 20.19 / 22.12.| Format | Read | Write | Status |
|---|---|---|---|
| WebP | ✅ | ✅ | Implemented & tested (simple + extended) |
| AVIF | ✅ | ✅ | Implemented & tested (ISOBMFF item + full iloc offset recalculation) |
| HEIC | ✅ | ✅ | Implemented & tested (shares AVIF's ISOBMFF engine; validated against the Nokia HEIF conformance suite) |
| JPEG | ✅ | ✅ | Implemented & tested (APP1 segment splice) |
| PNG | ✅ | ✅ | Implemented & tested (standard iTXt, CRC-correct) |
All four major web image formats are supported. An unrecognized format throws a typed UnsupportedFormatError rather than risking silent corruption. See docs/roadmap.md.
Being precise about what's spec-backed vs. forward-looking:
aeo-image writes exactly these fields — creator, credit, copyrightNotice, licenseUrl (xmpRights:WebStatement), and licensor (IPTC PLUS plus:Licensor).alt attribute, page context, and computer vision — not embedded metadata. → Image SEO best practices. So embedding alt text in the file complements (doesn't replace) your HTML alt; its value is durability, portability, accessibility, and attribution.In short: the documented, here-today win is portable, machine-readable attribution + licensing (which Google reads and recommends embedding) plus accessibility; the AI-search upside is a bet on where file-level metadata is heading.
aeo-image writes metadata as an Adobe XMP packet (the modern serialization) — not the legacy IPTC-IIM binary block. This is what Google and current tooling read.
Fields conform to the IPTC Photo Metadata Standard 2025.1 (the current revision), specifically the descriptive, accessibility, rights/licensing, and AI-provenance subset, across these namespaces:
| Namespace | Prefix | Used for |
|---|---|---|
| Dublin Core | dc: | description, title, subject/keywords, creator, rights |
| IPTC Core | Iptc4xmpCore: | AltTextAccessibility (IPTC 2021.1+) |
| IPTC Extension | Iptc4xmpExt: | digital source type + AI-generation provenance (IPTC 2025.1) |
| Adobe Photoshop | photoshop: | credit, copyright |
| XMP Rights | xmpRights: | web statement (license URL) |
| PLUS | plus: (ns 1.0) | licensor (license-acquisition link) |
This includes IPTC 2025.1's four AI-generation provenance properties (AIPromptInformation, AIPromptWriterName, AISystemUsed, AISystemVersionUsed) plus DigitalSourceType — see Label AI-generated images. Readable by exiftool (named tags from 13.40). C2PA / Content Credentials (cryptographically signed manifests) are a separate standard and out of scope.
npm install aeo-image
Requires Node ≥ 20 (or any modern runtime with TextEncoder/Uint8Array).
import { readMetadata } from "aeo-image";
import { readFileSync } from "node:fs";
const meta = readMetadata(new Uint8Array(readFileSync("photo.webp")));
console.log(meta.description, meta.keywords);
import { writeMetadata } from "aeo-image";
import { readFileSync, writeFileSync } from "node:fs";
const input = new Uint8Array(readFileSync("photo.webp"));
const output = writeMetadata(input, {
description: "Solar panels on a barn roof in rural Vermont",
title: "Rural Solar Install",
keywords: ["solar", "renewable energy", "Vermont", "agrivoltaics"],
altText: "Rows of black solar panels mounted on a red barn roof",
creator: "Jane Doe",
credit: "Example Studio",
rights: "© 2026 Example Studio",
});
writeFileSync("photo.tagged.webp", output);
IPTC Photo Metadata 2025.1 added four AI-provenance fields, which pair with
DigitalSourceType — the field ecosystems read to label an image as
AI-generated:
import { writeMetadata, DIGITAL_SOURCE_TYPE } from "aeo-image";
const output = writeMetadata(input, {
description: "A neon-lit street market at night in the rain",
digitalSourceType: DIGITAL_SOURCE_TYPE.trainedAlgorithmicMedia,
ai: {
prompt: "neon street market, rain reflections, cinematic 35mm",
promptWriter: "Jane Doe",
system: "DALL-E via Bing Image Creator",
systemVersion: "3",
},
// Per IPTC guidance, leave `creator` empty for fully AI-generated images —
// the prompt writer is explicitly not the image creator.
});
import { removeMetadata } from "aeo-image";
const clean = removeMetadata(input); // removes XMP/EXIF, keeps pixels + ICC
import { detectFormat } from "aeo-image";
detectFormat(buf); // "webp" | "jpeg" | "png" | "avif" | "heic" | "unknown"
| Function | Signature | Description |
|---|---|---|
readMetadata | (buf: Uint8Array) => ImageMetadata | Read semantic metadata. |
writeMetadata | (buf: Uint8Array, meta: ImageMetadata) => Uint8Array | Return a new buffer with metadata written; pixels preserved. |
removeMetadata | (buf: Uint8Array) => Uint8Array | Return a new buffer with XMP/EXIF stripped. |
detectFormat | (buf: Uint8Array) => ImageFormat | Identify the container by magic bytes. |
serializeXmp | (meta: ImageMetadata) => string | Build a standalone XMP packet (advanced). |
parseXmp | (xmp: string) => ImageMetadata | Parse a standalone XMP packet (advanced). |
ImageMetadata| Field | Type | Maps to |
|---|---|---|
description | string | dc:description (x-default) |
title | string | dc:title (x-default) |
keywords | string[] | dc:subject (rdf:Bag) |
creator | string | dc:creator (rdf:Seq) |
rights | string | dc:rights (x-default) |
altText | string | Iptc4xmpCore:AltTextAccessibility |
credit | string | photoshop:Credit |
copyrightNotice | string | photoshop:Copyright |
licenseUrl | string | xmpRights:WebStatement — Google Licensable |
licensor | { url, name? } | IPTC PLUS plus:Licensor — Google "Get this image" link |
digitalSourceType | string (IRI) | Iptc4xmpExt:DigitalSourceType — AI-generated disclosure; use DIGITAL_SOURCE_TYPE.* |
ai | { prompt?, promptWriter?, system?, systemVersion? } | IPTC 2025.1 Iptc4xmpExt:AIPromptInformation / AIPromptWriterName / AISystemUsed / AISystemVersionUsed |
The last three implement the fields Google Images reads for the Licensable badge and license link. All functions return a new buffer and never mutate the input. See docs/xmp-fields.md for the complete field/namespace reference and AEO rationale.
WebP is a RIFF container: a 12-byte header followed by a flat list of chunks. Metadata lives in a dedicated XMP chunk. Writing it means:
VP8 /VP8L), synthesize the extended-format VP8X header it needs to carry metadata — reading canvas dimensions straight from the bitstream.VP8X.XMP chunk and recompute the RIFF size.The compressed image chunk is copied byte-for-byte.
JPEG stores XMP in an APP1 marker segment (signature http://ns.adobe.com/xap/1.0/\0); PNG uses a standard iTXt chunk (XML:com.adobe.xmp, CRC-32 recomputed). Both follow the same splice pattern — locate/replace the metadata block, copy everything else (including the entropy-coded scan / IDAT data) byte-for-byte.
AVIF and HEIC are harder: they're ISOBMFF box trees (same container, different codec) where XMP is an item whose bytes are located via absolute file offsets in the iloc box. Inserting metadata shifts mdat, invalidating every offset — so aeo-image reads each item's bytes, emits a fresh meta (regenerated iinf/iloc/iref) and mdat, and recomputes all offsets from the new layout. The compressed image data is relocated verbatim (verified: decoded pixels are byte-identical before and after). The same engine handles HEIC, and was validated against the full Nokia HEIF conformance suite — including grid-tiled, overlay, thumbnail, and multi-item files.
Read docs/architecture.md, docs/webp-format.md, and docs/avif-format.md for the deep dives.
Runnable scripts in examples/:
01-read-write.mjs — tag an image and read it back.02-aeo-batch.mjs — batch-tag a folder of images for AEO.03-strip-metadata.mjs — strip metadata for privacy.04-cloud-function.mjs — request handler shape for edge/serverless.node examples/01-read-write.mjs
npm test # run the test suite (Node's built-in runner, no install needed on Node 22+)
npm run typecheck # type-check without emitting
npm run build # emit ESM + .d.ts to dist/
Tests run real .webp fixtures through full round-trips and validate RIFF framing, flag bits, and byte-level pixel preservation. Output is independently verified to parse in exiftool (in CI), and has been checked against ImageMagick and Apple's imaging stack.
Implementing JPEG, PNG, and AVIF is the active roadmap — see CONTRIBUTING.md and docs/roadmap.md. The architecture is designed so each new format is a thin adapter over a shared container/splice core.
FAQs
Write descriptive + rights metadata (captions, keywords, alt text, creator, license) into WebP, AVIF, HEIC, JPEG & PNG — self-describing images that Google Images reads and recommends embedding, built for the AI-search era. The only pure-JS, zero-dependen
The npm package aeo-image receives a total of 241 weekly downloads. As such, aeo-image popularity was classified as not popular.
We found that 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 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.

Research
More than 140 Mastra npm packages were compromised in a supply chain attack that used a typosquatted dependency to deliver a cross-platform infostealer during installation.