Add Watermarks to Images Programmatically — Protect Your Visual Assets

6 min read Image Generation

Your Images Are Being Stolen

If you publish original photography, product shots, or design work online, your images are being used without permission. Reverse image searches confirm what you already suspect — your work shows up on competitor sites, social media accounts, and stock photo aggregators.

Watermarking is the most practical defense. A semi-transparent logo or text overlay on every image makes unauthorized use obvious and attribution automatic. The problem is building the watermark pipeline.

The traditional approach: load the base image with Sharp or Pillow, load the watermark, composite them with opacity control, handle different image sizes, deploy the pipeline, maintain the dependencies. For every image. Forever.

The Image Generation API composites images as layers — including image overlays with opacity control. One API call replaces the entire watermark pipeline.

How Layer Composition Works

The Image Generation API renders images by stacking layers. Each layer has a type, a position, dimensions, and an opacity. For watermarking, you need two layers: the base image as a static-image layer, and the watermark as an image-overlay layer with reduced opacity.

import { IterationLayer } from "iterationlayer";

const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const baseImageBase64 = "..."; // your original image as base64
const watermarkBase64 = "..."; // your watermark logo as base64

const { data: { buffer: watermarkedBase64 } } = await client.generateImage({
  dimensions: { width: 1200, height: 800 },
  layers: [
    {
      type: "static-image",
      index: 0,
      file: { type: "base64", name: "photo.jpg", base64: baseImageBase64 },
      position: { x: 0, y: 0 },
      dimensions: { width: 1200, height: 800 },
      opacity: 100,
    },
    {
      type: "image-overlay",
      index: 1,
      file: { type: "base64", name: "watermark.png", base64: watermarkBase64 },
      opacity: 25,
    },
  ],
});

const watermarkedBuffer = Buffer.from(watermarkedBase64, "base64");

The static-image layer at index 0 is the base — your original photo, sized to fill the canvas. The image-overlay layer at index 1 stretches the watermark across the entire canvas at 25% opacity. The result is a full-coverage watermark that’s visible enough to deter theft but subtle enough to not ruin the image.

Adjusting Watermark Opacity

Opacity controls the tradeoff between protection and aesthetics. Lower opacity is more subtle. Higher opacity is harder to remove.

  • 15-20% — barely visible. Good for portfolio previews where the image quality matters more than protection.
  • 25-35% — the sweet spot for most use cases. Clearly present but doesn’t dominate the image.
  • 40-50% — aggressive protection. Use for high-value assets or preview images where the unwatermarked version is behind a paywall.
// Subtle watermark for portfolio display
{
  type: "image-overlay",
  index: 1,
  file: { type: "base64", name: "watermark.png", base64: watermarkBase64 },
  opacity: 20,
}

// Standard protection
{
  type: "image-overlay",
  index: 1,
  file: { type: "base64", name: "watermark.png", base64: watermarkBase64 },
  opacity: 30,
}

// Aggressive protection for previews
{
  type: "image-overlay",
  index: 1,
  file: { type: "base64", name: "watermark.png", base64: watermarkBase64 },
  opacity: 45,
}

Using URL-Based Files

If your images and watermarks are hosted on a CDN, you can reference them by URL instead of encoding as base64.

const result = await client.generateImage({
  dimensions: { width: 1200, height: 800 },
  layers: [
    {
      type: "static-image",
      index: 0,
      file: { type: "url", name: "photo.jpg", url: "https://cdn.example.com/photos/sunset.jpg" },
      position: { x: 0, y: 0 },
      dimensions: { width: 1200, height: 800 },
      opacity: 100,
    },
    {
      type: "image-overlay",
      index: 1,
      file: { type: "url", name: "watermark.png", url: "https://cdn.example.com/brand/watermark.png" },
      opacity: 30,
    },
  ],
});

This keeps request payloads small and avoids base64 encoding overhead. The API fetches the images from the provided URLs.

Batch Watermarking a Catalog

Watermarking a catalog of images follows the same parallel pattern as any batch operation.

const watermarkUrl = "https://cdn.example.com/brand/watermark.png";

const sourceImages = [
  { name: "photo-001.jpg", url: "https://cdn.example.com/photos/photo-001.jpg", width: 1200, height: 800 },
  { name: "photo-002.jpg", url: "https://cdn.example.com/photos/photo-002.jpg", width: 1600, height: 1200 },
  { name: "photo-003.jpg", url: "https://cdn.example.com/photos/photo-003.jpg", width: 1000, height: 1000 },
];

const watermarkedImages = await Promise.all(
  sourceImages.map(async (image) => {
    const { data: { buffer } } = await client.generateImage({
      dimensions: { width: image.width, height: image.height },
      layers: [
        {
          type: "static-image",
          index: 0,
          file: { type: "url", name: image.name, url: image.url },
          position: { x: 0, y: 0 },
          dimensions: { width: image.width, height: image.height },
          opacity: 100,
        },
        {
          type: "image-overlay",
          index: 1,
          file: { type: "url", name: "watermark.png", url: watermarkUrl },
          opacity: 30,
        },
      ],
    });

    return { name: image.name, buffer: Buffer.from(buffer, "base64") };
  })
);

Each image gets the same watermark overlay at the same opacity. The dimensions match each source image, so the watermark scales correctly regardless of image size.

Watermark Design Tips

The watermark image itself matters as much as the overlay logic.

Use a PNG with transparency. The watermark should be a logo or text on a transparent background. The API composites the watermark over the base image — any opaque background in the watermark will show as a solid block.

Design for full-canvas coverage. Since the image-overlay layer stretches across the entire canvas, design the watermark as a repeating pattern or a centered logo with generous transparent padding. A small logo in the center of a transparent canvas creates a single centered watermark. A repeating tile pattern creates a grid that’s harder to crop out.

Use white or light gray for the watermark graphic. Light watermarks are visible on both dark and light images. A dark watermark disappears on dark backgrounds.

Test at your target opacity. A watermark that looks perfect at 30% on a bright product photo might be invisible on a dark background. Test with a range of source images before committing to an opacity value.

Why an API Instead of a Library

Watermarking with Sharp or Pillow works in a script. It breaks down at scale. Sharp requires native compilation — npm install sharp fails on some platforms, some Docker base images, some CI environments. Pillow has similar issues with system-level image libraries.

The API is an HTTP endpoint. It works from any language, any platform, any environment that can make an HTTPS request. No native dependencies. No system packages. No compilation step.

For automated pipelines — an upload webhook that watermarks every new image, a nightly batch job that processes the day’s uploads, a content management system that watermarks on publish — the API keeps your application server doing what it’s good at (routing HTTP requests) and offloads the pixel manipulation.

Get Started

Check the docs for the full Image Generation API reference, including all layer types and their parameters. The TypeScript and Python SDKs handle authentication and response parsing.

Sign up for a free account — no credit card required. Upload a photo and a watermark, adjust the opacity, and see the result.

Start building in minutes

Free trial included. No credit card required.