png::meta tEXt · iTXt · zTXt

PNG Metadata API & Chunk Reference

Everything you need to read AI prompts and workflows from PNG files in your own code. This is the same logic that powers the PNGMeta extractor.

Where the data lives

PNG stores metadata in three ancillary chunk types. Each chunk is length (4) · type (4) · data · CRC (4). Text chunks carry a null-terminated Latin-1 keyword followed by the value:

ChunkEncodingLayout after keyword
tEXtLatin-1value
zTXtLatin-1, deflatecompression method (1 byte) · zlib stream
iTXtUTF-8, optional deflatecompression flag · method · language tag\0 · translated keyword\0 · value

Keys written by popular AI tools

ToolKeywordValue
ComfyUIpromptJSON — the executed API graph (node id → class_type, inputs)
ComfyUIworkflowJSON — the editable canvas graph (nodes, links, groups)
Automatic1111 / ForgeparametersText — prompt, `Negative prompt:`, then `Steps:, Sampler:, CFG scale:, Seed:, Size:, Model hash:, Model:`
InvokeAIinvokeai_metadataJSON — prompt, seed, model, scheduler
SwarmUIparametersJSON — `sui_image_params` object
Fooocusparameters / fooocus_schemeJSON — prompt, negative, styles, seed, base model
GenericSoftware, Comment, Description, XML:com.adobe.xmpEncoder name, free text, XMP packet

Copy-paste parser (JavaScript)

Works in every modern browser and in Node 18+ (Web Streams). Returns a plain object keyed by chunk keyword.

// Minimal PNG text-chunk reader (browser or Node ≥ 18)
const SIG = [137, 80, 78, 71, 13, 10, 26, 10];
const latin1 = new TextDecoder('latin1'), utf8 = new TextDecoder('utf-8');

async function inflate(bytes) {
  const s = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('deflate'));
  return new Uint8Array(await new Response(s).arrayBuffer());
}

export async function readPngText(buf) {           // buf: Uint8Array
  if (!SIG.every((b, i) => buf[i] === b)) throw new Error('not a PNG');
  const out = {}; let pos = 8;
  while (pos + 8 <= buf.length) {
    const len  = new DataView(buf.buffer, buf.byteOffset + pos).getUint32(0);
    const type = latin1.decode(buf.subarray(pos + 4, pos + 8));
    const data = buf.subarray(pos + 8, pos + 8 + len);
    if (type === 'tEXt') {
      const n = data.indexOf(0);
      out[latin1.decode(data.subarray(0, n))] = latin1.decode(data.subarray(n + 1));
    } else if (type === 'zTXt') {
      const n = data.indexOf(0);
      out[latin1.decode(data.subarray(0, n))] = latin1.decode(await inflate(data.subarray(n + 2)));
    } else if (type === 'iTXt') {
      const p = data.indexOf(0), compressed = data[p + 1] === 1;
      let q = data.indexOf(0, p + 3); q = data.indexOf(0, q + 1);
      const payload = data.subarray(q + 1);
      out[latin1.decode(data.subarray(0, p))] = utf8.decode(compressed ? await inflate(payload) : payload);
    }
    if (type === 'IEND') break;
    pos += 12 + len;                                // length + type + data + CRC
  }
  return out;                                       // { prompt, workflow, parameters, ... }
}

To get ComfyUI prompts, JSON.parse(out.prompt) and collect inputs.text from every node whose class_type contains CLIPTextEncode. For A1111/Forge, split out.parameters on "\nNegative prompt:" and the final settings line.

Hosted JSON endpoint

PNGMeta parses files client-side and does not currently expose a public server-side extraction endpoint. The snippet above is dependency-free and runs in browsers, Node, Deno, Bun and Cloudflare Workers, so you can embed the same logic in any pipeline or bot.

Not a developer? Just upload a PNG and read the result, or check the FAQ.