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

@enslo/sd-metadata

v2.2.0

Published

Read and write AI-generated image metadata

Readme

@enslo/sd-metadata

npm version npm downloads license

🌐 日本語版はこちら

🔗 Live Demo

A TypeScript library to read and write metadata embedded in AI-generated images.

Features

  • Multi-format Support: PNG (tEXt / iTXt), JPEG (COM / Exif), WebP (Exif)
  • Simple API: read(), write(), embed(), stringify() — four functions cover all use cases
  • TypeScript Native: Written in TypeScript with full type definitions included
  • Zero Dependencies: Works in Node.js and browsers without any external dependencies
  • Format Conversion: Seamlessly convert metadata between PNG, JPEG, and WebP
  • Metadata Preservation: Preserves original metadata structure when converting formats (e.g., PNG → JPEG → PNG maintains all original data)

Installation

npm install @enslo/sd-metadata

Tool Support

| Tool | PNG | JPEG | WebP | | ------ | :---: | :----: | :----: | | NovelAI * | ✅ | 🔄️ | ✅ | | ComfyUI * | ✅ | 🔄️ | 🔄️ | | Stable Diffusion WebUI | ✅ | ✅ | ✅ | | Forge | ✅ | ✅ | ✅ | | Forge Classic | ✅ | ✅ | ✅ | | Forge Neo | ✅ | ✅ | ✅ | | reForge | ✅ | ✅ | ✅ | | EasyReforge | ✅ | ✅ | ✅ | | SD.Next | ✅ | ✅ | ✅ | | InvokeAI | ✅ | 🔄️ | 🔄️ | | SwarmUI * | ✅ | ✅ | ✅ | | Civitai | ⚠️ | ✅ | ⚠️ | | TensorArt | ✅ | 🔄️ | 🔄️ | | Stability Matrix | ✅ | 🔄️ | 🔄️ | | HuggingFace Space | ✅ | 🔄️ | 🔄️ | | Fooocus | ⚠️ | ⚠️ | ⚠️ | | Ruined Fooocus | ✅ | 🔄️ | 🔄️ | | Easy Diffusion | ⚠️ | ⚠️ | ⚠️ | | Draw Things | ⚠️ | ⚠️ | ⚠️ |

Legend:

  • Fully Supported - Formats natively supported by the tool, verified with sample files
  • 🔄️ Extended Support - Formats not natively supported by the tool, but sd-metadata enables read/write through custom format conversion. Supports round-trip conversion back to native formats.
  • ⚠️ Experimental - Implemented by analyzing reference code or documentation, not verified with actual sample files. May not extract all metadata fields correctly.

Extended Support Examples:

  • Stability Matrix (native: PNG only) → sd-metadata enables JPEG/WebP support
  • NovelAI (native: PNG, WebP) → sd-metadata enables JPEG support

When you convert from a native format to an extended format and back (e.g., PNG → JPEG → PNG), all metadata is preserved.

[!NOTE] * Tools with format-specific behaviors. See Format-Specific Behaviors for details.

[!TIP] Help us expand tool support! We're actively collecting sample images from experimental tools (Easy Diffusion, Fooocus) and unsupported tools. If you have sample images generated by these or other AI tools, please consider contributing them! See CONTRIBUTING.md for details.

Format-Specific Behaviors

Some tools have specific behaviors when converting between formats:

  • ComfyUI JPEG/WebP: Reading supports multiple custom node formats (e.g., save-image-extended), but writing always uses the comfyui-saveimage-plus format for best information preservation and compatibility with ComfyUI's native drag-and-drop workflow loading.
  • NovelAI WebP: Automatically corrects corrupted UTF-8 in the Description field. WebP → PNG → WebP round-trip produces valid, readable metadata but with minor text corrections.
  • SwarmUI PNG→JPEG/WebP: Native SwarmUI JPEG/WebP files do not include node information. When converting from PNG, this library preserves the ComfyUI workflow in the Make field for complete metadata retention (extended support).

Import

ESM (TypeScript / Modern JavaScript):

import { read } from '@enslo/sd-metadata';

CommonJS (Node.js):

const { read } = require('@enslo/sd-metadata');

[!NOTE] All examples below use ESM syntax. CommonJS users can replace import with require.

Usage

Node.js Usage

import { read, stringify } from '@enslo/sd-metadata';
import { readFileSync } from 'fs';

const imageData = readFileSync('image.png');
const result = read(imageData);

if (result.status === 'success') {
  console.log('Tool:', result.metadata.software);       // 'novelai', 'comfyui', etc.
  console.log('Prompt:', result.metadata.prompt);
  console.log('Model:', result.metadata.model?.name);
  console.log('Size:', result.metadata.width, 'x', result.metadata.height);
}

// Format as human-readable text (works with any status)
const text = stringify(result);
if (text) {
  console.log(text);
}

Browser Usage

import { read, softwareLabels } from '@enslo/sd-metadata';

// Handle file input
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  const arrayBuffer = await file.arrayBuffer();
  const result = read(arrayBuffer);

  if (result.status === 'success') {
    document.getElementById('tool').textContent = softwareLabels[result.metadata.software];
    document.getElementById('prompt').textContent = result.metadata.prompt;
    document.getElementById('model').textContent = result.metadata.model?.name || 'N/A';
  }
});

Userscript Usage

For userscripts (Tampermonkey, Violentmonkey, etc.), load the IIFE build via @require:

// ==UserScript==
// @name        My Script
// @namespace   https://example.com
// @require     https://cdn.jsdelivr.net/npm/@enslo/[email protected]/dist/index.global.js
// ==/UserScript==

const response = await fetch(imageUrl);
const arrayBuffer = await response.arrayBuffer();
const result = sdMetadata.read(arrayBuffer);

if (result.status === 'success') {
  console.log('Tool:', result.metadata.software);
  console.log('Prompt:', result.metadata.prompt);
}

[!TIP] Always pin to a specific version in @require for stability.

Advanced Examples

Convert metadata between different image formats:

import { read, write } from '@enslo/sd-metadata';

// Read metadata from PNG
const pngData = readFileSync('comfyui-output.png');
const parseResult = read(pngData);

if (parseResult.status === 'success') {
  // Convert PNG to JPEG (using your preferred image processing library)
  const jpegImageData = convertToJpeg(pngData); // Pseudo-code: use sharp, canvas, etc.
  
  // Embed the metadata into the JPEG
  const result = write(jpegImageData, parseResult);
  
  if (result.ok) {
    writeFileSync('output.jpg', result.value);
    console.log('Image converted to JPEG with metadata preserved');
  }
}

Tip: This library handles metadata read/write only. For actual image format conversion (decoding/encoding pixels), use image processing libraries like sharp, jimp, or browser Canvas API.

import { read } from '@enslo/sd-metadata';

const result = read(imageData);

switch (result.status) {
  case 'success':
    // Metadata successfully parsed
    console.log(`Generated by ${result.metadata.software}`);
    console.log(`Prompt: ${result.metadata.prompt}`);
    break;

  case 'unrecognized':
    // Metadata exists but format is not recognized
    console.log('Unknown metadata format');
    // You can still access raw metadata for debugging:
    console.log('Raw chunks:', result.raw);
    break;

  case 'empty':
    // No metadata found
    console.log('No metadata in this image');
    break;

  case 'invalid':
    // Corrupted or invalid image data
    console.log('Error:', result.message);
    break;
}

When working with metadata from unsupported tools:

import { read, write } from '@enslo/sd-metadata';

const source = read(unknownImage);
// source.status === 'unrecognized'

// Write to target image
// - Same format (e.g., PNG → PNG): metadata preserved as-is
// - Cross-format (e.g., PNG → JPEG): metadata dropped with warning
const result = write(targetImage, source);
if (result.ok) {
  saveFile('output.png', result.value);
  if (result.warning) {
    // Metadata was dropped during cross-format conversion
    console.warn('Metadata was dropped:', result.warning.reason);
  }
}

To strip all metadata from an image:

import { write } from '@enslo/sd-metadata';

const result = write(imageData, { status: 'empty' });
if (result.ok) {
  writeFileSync('clean-image.png', result.value);
}

Create and embed custom metadata in A1111 format:

import { embed } from '@enslo/sd-metadata';

const metadata = {
  prompt: 'masterpiece, best quality, 1girl',
  negativePrompt: 'lowres, bad quality',
  width: 512,
  height: 768,
  sampling: {
    steps: 20,
    sampler: 'Euler a',
    cfg: 7,
    seed: 12345,
  },
  model: { name: 'model.safetensors' },
};

// Write to any image format (PNG, JPEG, WebP)
const result = embed(imageData, metadata);
if (result.ok) {
  writeFileSync('output.png', result.value);
}

You can also add arbitrary key-value pairs to the settings line with extras:

const result = embed(imageData, {
  ...metadata,
  extras: { Version: 'v1.10.0', 'Lora hashes': 'abc123' },
});

Tip: If an extras key matches a structured field (e.g., Steps), the extras value overrides the structured value at its original position. New keys are appended at the end.

Since EmbedMetadata is a subset of all GenerationMetadata variants, you can pass parsed metadata directly — including NovelAI with its characterPrompts:

import { read, embed } from '@enslo/sd-metadata';

const result = read(novelaiPng);
if (result.status === 'success') {
  // NovelAI metadata (or any other tool) works as-is
  const output = embed(blankJpeg, result.metadata);
}

Convert a ParseResult to a human-readable string. Automatically selects the best representation based on status:

import { read, stringify } from '@enslo/sd-metadata';

const result = read(imageData);
const text = stringify(result);
if (text) {
  console.log(text);

  // For 'success': outputs in WebUI format:
  // masterpiece, best quality, 1girl
  // Negative prompt: lowres, bad quality
  // Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 12345, Size: 512x768, Model: model.safetensors
  //
  // For 'unrecognized': outputs raw metadata text
  // For 'empty' / 'invalid': returns empty string
}

API Reference

read(input: Uint8Array | ArrayBuffer, options?: ReadOptions): ParseResult

Reads and parses metadata from an image file.

Parameters:

  • input - Image file data (PNG, JPEG, or WebP)
  • options - Optional read options (see Type Documentation for details)

Returns:

  • { status: 'success', metadata, raw } - Successfully parsed
    • metadata: Unified metadata object (see GenerationMetadata)
    • raw: Original format-specific data (chunks/segments)
  • { status: 'unrecognized', raw } - Image has metadata but not from a known AI tool
    • raw: Original metadata preserved for conversion
  • { status: 'empty' } - No metadata found in the image
  • { status: 'invalid', message? } - Corrupted or unsupported image format
    • message: Optional error description

write(input: Uint8Array | ArrayBuffer, metadata: ParseResult): WriteResult

Writes metadata to an image file.

Parameters:

  • input - Target image file data (PNG, JPEG, or WebP)
  • metadata - ParseResult from read()
    • status: 'success' or 'empty' - Can write directly
    • status: 'unrecognized' - Same format: writes as-is; Cross-format: drops metadata with warning

Returns:

  • { ok: true, value: Uint8Array, warning?: WriteWarning } - Successfully written
    • warning is set when metadata was intentionally dropped (e.g., unrecognized cross-format)
  • { ok: false, error: { type, message? } } - Failed. type is one of:
    • 'unsupportedFormat': Target image is not PNG, JPEG, or WebP
    • 'conversionFailed': Metadata conversion failed (e.g., incompatible format)
    • 'writeFailed': Failed to embed metadata into the image

embed(input: Uint8Array | ArrayBuffer, metadata: EmbedMetadata | GenerationMetadata): WriteResult

Embeds custom metadata into an image in SD WebUI (A1111) format.

Parameters:

  • input - Target image file data (PNG, JPEG, or WebP)
  • metadata - EmbedMetadata or GenerationMetadata to embed (use extras field for custom settings)

Returns:

  • { ok: true, value: Uint8Array } - Successfully written (returns new image data)
  • { ok: false, error: { type, message? } } - Failed. type is one of:
    • 'unsupportedFormat': Target image is not PNG, JPEG, or WebP
    • 'writeFailed': Failed to embed metadata into the image

Use cases:

  • Creating custom metadata for programmatically generated images
  • Converting metadata from other tools to WebUI-compatible format
  • Building applications that output WebUI-readable metadata

stringify(input: ParseResult | EmbedMetadata | GenerationMetadata): string

Converts metadata to a human-readable string.

Parameters:

  • input - One of:
    • ParseResult from read() — selects best representation based on status
    • EmbedMetadata or GenerationMetadata — formats as A1111 text directly

Returns:

  • ParseResult with success → Human-readable text in WebUI format
  • ParseResult with unrecognized → Raw metadata as plain text
  • ParseResult with empty / invalid → Empty string
  • EmbedMetadata / GenerationMetadata → Human-readable text in WebUI format

Use cases:

  • Displaying generation parameters in image viewers or galleries
  • Copying metadata to clipboard as readable text
  • Logging or debugging parsed metadata

softwareLabels: Record<GenerationSoftware, string>

A read-only mapping from GenerationSoftware identifiers to their human-readable display names.

import { softwareLabels } from '@enslo/sd-metadata';

const result = read(imageData);
if (result.status === 'success') {
  console.log(softwareLabels[result.metadata.software]);
  // => "NovelAI", "ComfyUI", "Stable Diffusion WebUI", etc.
}

Type Reference

This section provides an overview of the main types. For complete type definitions, see Type Documentation.

ParseResult

The result of the read() function. It uses a discriminated union with a status field.

type ParseResult =
  | { status: 'success'; metadata: GenerationMetadata; raw: RawMetadata }
  | { status: 'unrecognized'; raw: RawMetadata }
  | { status: 'empty' }
  | { status: 'invalid'; message?: string };

BaseMetadata

Common fields shared by all metadata types. This interface is also the foundation of EmbedMetadata.

interface BaseMetadata {
  prompt: string;
  negativePrompt: string;
  width: number;
  height: number;
  model?: ModelSettings;
  sampling?: SamplingSettings;
  hires?: HiresSettings;
  upscale?: UpscaleSettings;
}

GenerationMetadata

Unified metadata structure returned by the read() function. This is a discriminated union of 3 specific metadata types, distinguished by the software field. All types extend BaseMetadata.

Metadata Type Variants:

  • NovelAIMetadata (software: 'novelai')
    Includes NovelAI-specific fields for V4 character placement:

    • characterPrompts?: CharacterPrompt[] - Per-character prompts with positions
    • useCoords?: boolean - Use character coordinates for placement
    • useOrder?: boolean - Use character order
  • ComfyUIMetadata (software: 'comfyui' | 'tensorart' | 'stability-matrix' | 'swarmui')
    Includes ComfyUI workflow graph:

    • nodes: ComfyNodeGraph (required for comfyui/tensorart/stability-matrix)
    • nodes?: ComfyNodeGraph (optional for swarmui - only in PNG format)
  • StandardMetadata (software: 'sd-webui' | 'forge' | 'forge-classic' | 'reforge' | 'invokeai' | ...) Baseline metadata without tool-specific extensions. Used by most SD WebUI-based tools.

Type Definition:

type GenerationMetadata =
  | NovelAIMetadata
  | ComfyUIMetadata
  | StandardMetadata;

Usage Example:

const result = read(imageData);

if (result.status === 'success') {
  const metadata = result.metadata;
  
  // Access common fields
  console.log('Prompt:', metadata.prompt);
  console.log('Model:', metadata.model?.name);
  console.log('Seed:', metadata.sampling?.seed);
  
  // Type-specific handling using discriminated union
  if (metadata.software === 'novelai') {
    // TypeScript knows this is NovelAIMetadata
    console.log('Character prompts:', metadata.characterPrompts);
  } else if (
    metadata.software === 'comfyui' ||
    metadata.software === 'tensorart' ||
    metadata.software === 'stability-matrix'
  ) {
    // TypeScript knows this is BasicComfyUIMetadata (nodes always present)
    console.log('Node count:', Object.keys(metadata.nodes).length);
  } else if (metadata.software === 'swarmui') {
    // TypeScript knows this is SwarmUIMetadata (nodes optional)
    if (metadata.nodes) {
      console.log('Workflow included');
    }
  }
}

See Type Documentation for detailed interface definitions of each metadata type.

GenerationSoftware

String literal union of all supported software identifiers. Used as the key type for softwareLabels.

type GenerationSoftware =
  | 'novelai' | 'comfyui' | 'swarmui' | 'tensorart' | 'stability-matrix'
  | 'sd-webui' | 'forge' | 'forge-classic' | 'forge-neo' 
  | 'reforge'| 'easy-reforge' | 'sd-next' | 'civitai' | 'hf-space'
  | 'invokeai' | 'easydiffusion' | 'fooocus' | 'ruined-fooocus'
  | 'draw-things';

EmbedMetadata

User-created custom metadata for the embed() and stringify() functions. While GenerationMetadata represents parsed output from a known AI tool, EmbedMetadata is designed for composing metadata from scratch. Extends BaseMetadata with optional character prompts and extras.

type EmbedMetadata = BaseMetadata &
  Pick<NovelAIMetadata, 'characterPrompts'> & {
    extras?: Record<string, string | number>;
  };

RawMetadata

Preserves the original metadata structure for round-trip conversions.

type RawMetadata =
  | { format: 'png'; chunks: PngTextChunk[] }
  | { format: 'jpeg'; segments: MetadataSegment[] }
  | { format: 'webp'; segments: MetadataSegment[] };

For detailed documentation of all exported types including ModelSettings, SamplingSettings, and format-specific types, see the Type Documentation.

Development

See the Contributing Guide for development setup and guidelines.

License

MIT