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).
- TypeScript
- PHP
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');
$logo = $client->file('logo.png');
$client->file('photo.jpg')
->watermark($logo, ['anchor' => 'bottom_right', 'opacity' => 0.6])
->run(maxWait: '2m')
->toFile('photo-wm.jpg');
Which media route where
watermark() routes on the base file's media — you use the same verb either
way:
| Base file | Wire operation | Status |
|---|---|---|
image/jpeg, image/png, image/webp | image_watermark | Stable |
image/tiff, image/bmp | image_watermark | Stable |
video/mp4, video/webm | video_watermark | Beta |
image/gif | image_watermark | Planned — 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:
- TypeScript
- PHP
const result = await client
.file('clip.mp4')
.watermark(logo, { anchor: 'top_right', overlay_width: '20%' })
.run({ maxWait: '5m' });
$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.
| Option | Type | Notes |
|---|---|---|
anchor | anchor | Where the overlay sits. |
margin_x / margin_y | string | Offset from the anchor — '40px' or '5%'. |
opacity | number | 0–1. |
overlay_width | string | '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:
- TypeScript
- PHP
import { fileInput } from '@giveitsmaller/sdk';
const logo = client.file(fileInput.uploadId('file_...'));
use Gisl\Sdk\FileFirst\FileInput;
$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.
- TypeScript
- PHP
const result = await client
.file('flyer.png')
.textWatermark('CONFIDENTIAL', { anchor: 'center', opacity: 0.5 })
.run({ maxWait: '2m' });
await result.toFile('flyer-wm.png');
$client->file('flyer.png')
->textWatermark('CONFIDENTIAL', ['anchor' => 'center', 'opacity' => 0.5])
->run(maxWait: '2m')
->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.
- TypeScript
- PHP
await client
.file('flyer.png')
.textWatermark('DRAFT', {
watermark_mode: 'tiled',
tile_spacing: 120,
rotation: -30,
opacity: 0.3,
})
.run({ maxWait: '2m' });
$client->file('flyer.png')
->textWatermark('DRAFT', [
'watermark_mode' => 'tiled',
'tile_spacing' => 120,
'rotation' => -30,
'opacity' => 0.3,
])
->run(maxWait: '2m');
Options
| Option | Type | Notes |
|---|---|---|
font_size | number | 8–512 pixels. |
color | string | Hex RGB/RGBA — '#FFFFFF80'. |
font_family | 'liberation_sans' | Bundled font. |
rotation | number | Degrees, -360–360. |
watermark_mode | 'single' | 'tiled' | Rendering mode. |
tile_spacing | number | Gap between tiled labels, in pixels. |
anchor | anchor | Same 9-grid as above. |
margin_x / margin_y | string | Offset from the anchor. |
opacity | number | 0–1. |
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.
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.