Mememage Encode
Stamp a 2-pixel proof of what made your image
- image
- image
- identifier
- record
This is the node the whole pack is named after, and it does something genuinely unusual in ComfyUI: it doesn't rely on the PNG metadata that usually carries your workflow. ComfyUI bakes the entire graph into every saved image as text chunks, and the moment that image is re-encoded - Discord, Reddit, Instagram, a JPEG conversion, a screenshot - that metadata is gone forever. Mememage Encode takes a different bet: it writes your provenance into the pixels themselves, as a two-pixel-tall bar along the bottom edge. Pixels survive re-encoding. Metadata doesn't.
What it actually does
You wire in an image and it hands you three things out:
image- your image with the bar stamped in. Wire this into Save Image (or Mememage Save Record) and save.identifier- a key to a record you store wherever you like: a folder, a CDN, IPFS, the Internet Archive. The identifier is access.record- a plain JSON object of whatever fields you chose: your generation data, a creator name, a license, anything.
The bar carries the identifier plus a content hash - a fingerprint of the record. Change a field in the record and it no longer matches the image's bar, which is how you later prove the data belongs to this exact image and hasn't been touched (that's Mememage Verify's job).
Nothing here is a watermark you'd notice - at two pixels it reads as a hairline at the bottom of the image. And despite the mystical name, there's no API, no account, no key. It's all local: mememage.encode() in memory, no disk round-trip.
The inputs that matter
image- required, the only one. Wire your VAEDecode output here.fields- the socket for a Mememage Fields / Field / Workflow Fields node. This is where your record data comes from. Values here override matching keys you type infields_json.fields_json- the inline alternative: type a JSON object directly if you don't want to build fields up node-by-node.embed_workflow(on by default) - stores the generating graph in the record ascomfy_prompt. This is the killer feature: the workflow rides inside the verifiable record, so it survives even when the PNG's own metadata is stripped.use_identifier+identifier- this pair is where people get confused. Off by default, meaning the node is content-addressed: change the image, get a fresh identity and a fresh record. That's right for finished work. Flipuse_identifieron and pin an identifier (from a Mememage Reserve ID node, or paste<prefix>-<16 hex>) and you iterate one piece - every run overwrites the same record while the hash tracks what actually changed. Content-addressing is the sane default; the toggle just lets you leave a pin wired in and opt in deliberately.password_file/private- for encryption. There is deliberately no plaintext password field: the password comes from a file path or theMEMEMAGE_PASSWORDenv var only, so it can never ride the graph into the PNG metadata.privateis a comma-separated list of field names to encrypt; leave it empty with a password set and everything gets encrypted (AES-256).
Installing it
This ships in mememage-comfy, a small MIT-licensed pack with a light footprint. Easiest is ComfyUI Manager: search "Mememage" → Install → restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/sememtac/mememage-comfy
# install the core library into ComfyUI's OWN python:
<ComfyUI-python> -m pip install mememage
Restart and you'll find it under the Mememage category. The mememage core does all the bar work; Manager pulls it from requirements.txt for you. One optional extra: encryption needs pip install "mememage[encrypt]" (the cryptography library). The pack also pins certifi for HTTPS - that's for the Fetch Record node, not this one.
Where people get burned
- The PNG metadata leak. ComfyUI's stock SaveImage bakes the whole graph into the file. If you typed a secret into a node widget, its plaintext rides along. The password can't leak (file/env only), but other field values can. The clean path: wire Encode's
imageinto Mememage Save Record, which writes the PNG with no metadata at all - or run ComfyUI with--disable-metadata, or just don't type secrets into widgets. - Encryption silently does nothing if
cryptographyisn't installed - the record stays plaintext. Check the backend log. - This pack is new and quiet (zero community threads in the reddit corpus we search), so don't expect a support forum. The README and node tooltips are unusually good - trust them.
The minimal flow: CheckpointLoader → KSampler → VAEDecode → Encode → SaveImage, with record wired into Save Record. The repo's two example workflows drag straight onto the canvas to show it wired.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| fieldsopt | STRING | Wire a Mememage Fields / Field node here. Overrides matching keys typed in fields_json. | |
| fields_jsonopt | STRING | {} | — |
| prefixopt | STRING | mememage | — |
| embed_workflowopt | BOOLEAN | true | Embed the generating graph in the record as comfy_prompt. When you also encrypt fields, the graph is sealed with them — its widget values ARE those fields' plaintext. |
| encrypt_workflowopt | BOOLEAN | false | Seal the embedded workflow (comfy_prompt) too. Encrypting ANY field now does this automatically — the graph carries that field's plaintext — so this is belt-and-braces there. On its own it's a no-op: with no `private` list, a password already encrypts every field including the workflow. To seal ONLY the recipe and keep your other fields public, put `comfy_prompt` in `private` instead. |
| password_fileopt | STRING | To encrypt, put your passphrase in a file and give its PATH here (or set the MEMEMAGE_PASSWORD env var). The path rides the graph — the password never does, so it can't leak into the PNG metadata. Empty = public record. Needs mememage[encrypt]. | |
| privateopt | STRING | Comma-separated top-level field names to encrypt. Empty + a password = encrypt EVERY field. Encrypting anything also seals the embedded workflow, which would otherwise carry those fields' plaintext. | |
| use_identifieropt | BOOLEAN | false | OFF (default): content-address — a fresh identity per change, ignoring the identifier below EVEN IF a Reserve ID wire is connected. ON: honor the wired/typed identifier to iterate ONE piece (each conceive overwrites the same record). Lets you leave a pin connected but opt in to it deliberately, instead of disconnecting the wire. |
| identifieropt | STRING | Pin a reserved identifier (wire a Mememage Reserve ID node, or paste a <prefix>-<16 hex>) to keep iterating ONE piece — each conceive overwrites the SAME record. Honored only when the 'use_identifier' toggle above is ON. Empty = content-addressed (a fresh identity per change). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| identifier | STRING | — |
| record | STRING | — |