Text Overlay
Text on Images in ComfyUI, Without Installing the Kitchen Sink
- image
- IMAGE
You want a watermark, a caption, or your sampler settings burned into an image - and you really don't want to install a 300-node utility suite to do it. That's the hole this node fills. Text Overlay is one tiny class from Munkyfoot/ComfyUI-TextOverlay that draws text on an image and hands you back the same IMAGE tensor, ready to save or keep flowing down your graph.
It's a rebuild of the old mikkel/ComfyUI-text-overlay plugin, expanded with stroke control, alignment, and batch support. Where a pack like WAS Node Suite bundles text overlay as one utility among dozens, this is the focused single-purpose version. If you already run WAS, you don't need this. If you don't, it's a far lighter way to get the job done - one node instead of a dependency graph.
What you'd actually use it for
- Watermarking a batch of images before you upload them anywhere.
- Burning generation settings - sampler, scheduler, seed, prompt - onto saved images. People do this a lot; you hook the node up to the strings from your workflow (Widget To String in KJNodes is a common bridge) and the output carries the info with the image.
- Subtitle-style captions on frame sequences or video frames, since it processes whole batches in one pass.
The niche reputation is earned: in an r/comfyui thread about typography, this repo is literally the top recommendation for putting crisp text on images in Comfy.
How it works
No models, no API, no weights, no GPU involved. The node converts the IMAGE tensor to a PIL image, does an ImageDraw.text() call with your settings, and converts back. That's the whole mechanism - which is why it's instant and why it works fully offline.
Two details the UI doesn't make obvious. First, stroke_thickness is a 0–1 float, not pixels. The real stroke is int(font_size × stroke_thickness × 0.5), so the default 0.2 at font size 32 is a 3-pixel outline. If your stroke looks invisible or absurd, that's the scale to blame. Second, text auto-wraps to the image width minus 2 × padding, and your newlines are respected, so long captions wrap instead of spilling off the edge.
Inputs that matter
The full list from the schema, but the ones you'll actually set most runs:
image- your IMAGE tensor.text- multiline string; the default is "Hello", so change it.font_size- integer, 1–9999.fill_color_hexandstroke_color_hex- hex strings like#FFFFFF. The code happily strips the#and even accepts 3-digit hex, so don't stress about format.font- the gotcha, see below.horizontal_alignment/vertical_alignment- left/center/right and top/middle/bottom, withx_shift/y_shift(±128) for fine nudges.line_spacing- spacing between wrapped lines.
The single output is IMAGE, same shape as the input - wire it straight into Save Image, Preview Image, or a video encoder.
Installing it
From ComfyUI Manager, search for "ComfyUI-TextOverlay" and install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Munkyfoot/ComfyUI-TextOverlay
Then restart ComfyUI. There's no requirements.txt and nothing heavy to download - it only uses PIL, numpy, and torch, all of which ship with ComfyUI already.
The font gotcha
Here's where people get burned. The repo ships no fonts directory, and the default font value is ariblk.ttf (Arial Black). On Windows, PIL finds that name in the system fonts folder, so it works out of the box. On Linux or macOS it doesn't, and the node silently falls back to Pillow's default font - which is ugly, and looks nothing like what you asked for.
Fix it by making the folder yourself:
mkdir -p ComfyUI/custom_nodes/ComfyUI-TextOverlay/fonts
# drop arial.ttf or anything else in there
Then type the filename (e.g. arial.ttf) into the font widget. Or just type the name of any system-installed font - the code falls back to a system-font lookup if the file isn't in fonts/. If a font truly can't load, it prints an error to the console and uses the default rather than crashing.
One more thing worth knowing: the node caches the wrapped text and position during batch processing, so stamping the same caption across a whole batch is fast. If it ever looks like a widget change didn't take, that's the only place caching could bite - re-run and it recalcs.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| text | STRING | Hello | — |
| font_size | INT | 321–9999 | — |
| font | STRING | ariblk.ttf | — |
| fill_color_hex | STRING | #FFFFFF | — |
| stroke_color_hex | STRING | #000000 | — |
| stroke_thickness | FLOAT | 0.200–1 | — |
| padding | INT | 160–128 | — |
| horizontal_alignment | COMBO | center | 3 options: left, center, right |
| vertical_alignment | COMBO | bottom | 3 options: top, middle, bottom |
| x_shift | INT | 0-128–128 | — |
| y_shift | INT | 0-128–128 | — |
| line_spacing | FLOAT | 4.00–50 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |