Skip to main content

Watermark

Two different verbs, depending on whether your watermark is an image or text:

  • watermark(overlay, options) — composites a second image onto your file. Multi-input: the overlay is its own file. Image or video base.
  • textWatermark(text, options) — stamps a text label. Single-input: the text is an option, not a file. Image base only.

There is no document watermarking — see What cannot be watermarked.

Image overlay — watermark()

The overlay is a file-node, exactly like your base file. Pass the node itself — there are no string input refs. You can process the overlay first if you want (it is a recipe like any other).

const logo = client.file('logo.png');

const result = await client
.file('photo.jpg')
.watermark(logo, { anchor: 'bottom_right', opacity: 0.6 })
.run({ maxWait: '2m' });

await result.toFile('photo-wm.jpg');

Which media route where

watermark() routes on the base file's media — you use the same verb either way:

Base fileWire operationStatus
image/jpeg, image/png, image/webpimage_watermarkStable
image/tiff, image/bmpimage_watermarkStable
video/mp4, video/webmvideo_watermarkBeta
image/gifimage_watermarkPlanned — not yet available

Those are the only accepted types. Other video containers (MOV and friends) are rejected locally, before upload.

A video base needs no different call:

const result = await client
.file('clip.mp4')
.watermark(logo, { anchor: 'top_right', overlay_width: '20%' })
.run({ maxWait: '5m' });

What cannot be watermarked

Documents, audio and animated GIF have no watermark verb you can use today. Some routes are planned in the contract (a dedicated audio-watermark operation, and image/gif on image_watermark) but are not yet exposed here; document watermarking has no path at all.

textWatermark() is not an alternative for these — it is image-only (unlike watermark(), which additionally takes video, textWatermark() does not take document, audio or video). If you have a PDF, there is currently no watermark verb for it. When you hand watermark() a document base, the SDK rejects it before upload and the error says exactly that.

The pre-upload gate

An unsupported base throws locally, before anything uploads — you do not pay a round trip to discover that a PDF cannot take an image overlay. The SDK checks as soon as it knows the base media: usually at the .watermark() call itself, and otherwise just before upload on run() / submit() (when the base media cannot be detected up front, such as a stream).

Options

anchor takes a 9-grid position: top_left, top_center, top_right, center_left, center, center_right, bottom_left, bottom_center, bottom_right.

OptionTypeNotes
anchoranchorWhere the overlay sits.
margin_x / margin_ystringOffset from the anchor — '40px' or '5%'.
opacitynumber01.
overlay_widthstring'120px' or '20%'.

Unknown keys are rejected before upload.

One overlay per call: watermark() takes a single overlay node. The contract describes a multi-overlay stack for image_watermark, but the SDK does not yet expose a way to supply more than one overlay, so it is not usable from either SDK today.

Reusing an overlay

To avoid re-uploading the same overlay on every run, upload it once and reference it by id. An uploadId input is passed through without being uploaded again:

import { fileInput } from '@giveitsmaller/sdk';

const logo = client.file(fileInput.uploadId('file_...'));

Within a single run there is no upload de-duplication for watermark inputs — the base and the overlay are always uploaded separately, even if they are the same file.

Text label — textWatermark()

No second file: the text is the first argument.

const result = await client
.file('flyer.png')
.textWatermark('CONFIDENTIAL', { anchor: 'center', opacity: 0.5 })
.run({ maxWait: '2m' });

await result.toFile('flyer-wm.png');

Accepts image/jpeg, image/png, image/webp, image/tiff and image/bmp — all stable. No video, audio or document support.

Tiling

watermark_mode: 'tiled' repeats the label across the image; tile_spacing sets the gap in pixels.

await client
.file('flyer.png')
.textWatermark('DRAFT', {
watermark_mode: 'tiled',
tile_spacing: 120,
rotation: -30,
opacity: 0.3,
})
.run({ maxWait: '2m' });

Options

OptionTypeNotes
font_sizenumber8512 pixels.
colorstringHex RGB/RGBA — '#FFFFFF80'.
font_family'liberation_sans'Bundled font.
rotationnumberDegrees, -360360.
watermark_mode'single' | 'tiled'Rendering mode.
tile_spacingnumberGap between tiled labels, in pixels.
anchoranchorSame 9-grid as above.
margin_x / margin_ystringOffset from the anchor.
opacitynumber01.

Passing text in the options bag is rejected — the first argument owns it.

One operation per job

Both watermark operations must be the only operation in their job. To combine a watermark with, say, a compress, model them as two runs: watermark first, then compress the result.

warning

Both SDKs currently let you chain .compress() / .convert() / .thumbnail() directly after .watermark(), but doing so builds a single job containing both operations — which the contract forbids. Avoid post-watermark chaining until this is fixed.

See also

  • Image output — quality, target size and resize via output().
  • Thumbnail — a separate preview artifact, rather than a stamp.