Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Sharp’s composite() method to place a transparent text layer over your screenshot, then encode the result with png(), jpeg(), or another output method. The most portable approach is to create the watermark as an SVG buffer, which gives you control over fonts, color, opacity, background shapes, and layout without creating a separate image file.

This guide shows a complete Node.js implementation, exact and semantic positioning, generated-text alternatives, batch processing, format selection, troubleshooting, and an API option when you would rather not configure a browser capture pipeline.

What you need

  • Node.js 20.9.0 or later, or another runtime compatible with Sharp’s Node-API v9 requirement.
  • A screenshot file such as screenshot.png.
  • A project using ECMAScript modules or an equivalent CommonJS import.

Install Sharp with:

npm install sharp

Sharp uses the host system’s font configuration when rendering SVG and generated text. A font named in your watermark must therefore be installed or otherwise available to the machine running the script.

Minimal watermark: transparent SVG over a screenshot

Create watermark.mjs:

import sharp from 'sharp';

const watermark = Buffer.from(`
  <svg width="420" height="90">
    <text x="16" y="56" font-family="Arial, sans-serif" font-size="30"
          fill="white" fill-opacity="0.72">© Example</text>
  </svg>
`);

await sharp('screenshot.png')
  .composite([{ input: watermark, gravity: 'southeast' }])
  .png()
  .toFile('screenshot-watermarked.png');

Run it with:

node watermark.mjs

The output is a new PNG named screenshot-watermarked.png. The source file is not modified. The SVG has a transparent background, so only the text is composited. gravity: 'southeast' places the overlay in the bottom-right corner.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How Sharp compositing works

sharp(input) creates a processing pipeline for the base image. composite(images) then places one or more overlays on the processed image. Every overlay must be the same size as or smaller than that processed image.

An overlay object can contain an image buffer, a file path, or generated input, together with placement and blending options. You can pass several overlay objects in one array, allowing a logo, copyright notice, and a timestamp to be added in one operation.

Semantic placement with gravity

  • northwest: top-left.
  • north: centered at the top.
  • northeast: top-right.
  • west, centre, or east: vertically centered.
  • southwest: bottom-left.
  • south: centered at the bottom.
  • southeast: bottom-right.

Gravity is useful when the screenshot dimensions vary. The watermark keeps its relationship to the edge instead of requiring you to calculate coordinates for every image.

Exact pixel offsets

For a fixed margin or a design specification, supply integer top and left values:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await sharp('screenshot.png')
  .composite([
    { input: watermark, top: 20, left: 20 }
  ])
  .png()
  .toFile('screenshot-top-left.png');

When both top and left are supplied, they take precedence over gravity. Use offsets when you need a watermark exactly 20 pixels from two edges, regardless of its semantic corner.

Build a more readable watermark with an SVG background

White text can disappear over a pale page, while black text can disappear over a dark page. Add a translucent rectangle behind the text:

import sharp from 'sharp';

const watermark = Buffer.from(`
  <svg width="460" height="104" viewBox="0 0 460 104">
    <rect x="0" y="0" width="460" height="104" rx="12"
          fill="#000000" fill-opacity="0.55" />
    <text x="22" y="66" font-family="Arial, sans-serif"
          font-size="30" fill="#ffffff">© Example Studio</text>
  </svg>
`);

await sharp('screenshot.png')
  .composite([{ input: watermark, gravity: 'southeast' }])
  .png()
  .toFile('screenshot-watermarked.png');

The SVG dimensions are part of the overlay. Keep them comfortably within the screenshot dimensions; an overlay larger than the processed base image cannot be composited as intended.

Use Sharp’s generated-text input instead of SVG

Sharp also supports text generated from an input.text object. This is convenient when you only need text and do not want to write SVG markup. The text input supports UTF-8 content, font selection, width and height limits, alignment, DPI, RGBA output, and line spacing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import sharp from 'sharp';

const textLayer = {
  text: {
    text: '© Example Studio',
    font: 'Arial',
    fontSize: 30,
    rgba: true,
    align: 'center'
  },
  width: 360,
  height: 70,
  channels: 4
};

await sharp('screenshot.png')
  .composite([
    { input: textLayer, gravity: 'southeast' }
  ])
  .png()
  .toFile('screenshot-text-input.png');

Choose generated text for a straightforward label. Choose SVG when you need a backing rectangle, rounded corners, multiple text runs, custom shapes, or styling that is easier to express declaratively.

Resize or crop before adding the watermark

Geometry operations in the same Sharp pipeline—such as resize, rotate, flip, flop, and extract—are applied to the input image before composition. Put those operations before composite() so the watermark is positioned on the final dimensions:

import sharp from 'sharp';

const watermark = Buffer.from(`
  <svg width="420" height="90">
    <text x="16" y="56" font-family="Arial" font-size="30"
          fill="white" fill-opacity="0.72">© Example</text>
  </svg>
`);

await sharp('screenshot.png')
  .resize({ width: 1600, withoutEnlargement: true })
  .rotate(2)
  .composite([{ input: watermark, gravity: 'southeast' }])
  .webp({ quality: 88 })
  .toFile('screenshot-watermarked.webp');

If you composite first and then resize, the watermark becomes part of the pixels being resized. Its size, sharpness, and margin will change with the image, which is usually not what you want.

Save as PNG, JPEG, WebP, or another format

Select the encoder explicitly at the end of the pipeline:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • .png() preserves sharp text and interface edges without lossy compression.
  • .jpeg({ quality: 85 }) is useful when a smaller photographic file is more important than pixel-perfect UI edges.
  • .webp({ quality: 88 }) provides a modern compressed image format.
  • Sharp also supports AVIF, TIFF, GIF, and raw pixel output.
await sharp('screenshot.png')
  .composite([{ input: watermark, gravity: 'southeast' }])
  .jpeg({ quality: 85, progressive: true })
  .toFile('screenshot-watermarked.jpg');

If you do not select a format, Sharp normally preserves the input format; SVG input is an exception and is written as PNG.

Watermark several screenshots

Construct the watermark once and reuse it for each input. This keeps the design consistent; measure your own workload before assuming a particular throughput.

import sharp from 'sharp';

const watermark = Buffer.from(`
  <svg width="420" height="90">
    <rect width="420" height="90" rx="10" fill="#000" fill-opacity="0.45" />
    <text x="16" y="56" font-family="Arial" font-size="30" fill="#fff">
      © Example
    </text>
  </svg>
`);

const files = ['one.png', 'two.png', 'three.png'];

await Promise.all(files.map((file) =>
  sharp(file)
    .composite([{ input: watermark, gravity: 'southeast' }])
    .png()
    .toFile(file.replace(/.png$/i, '-watermarked.png'))
));

For very large batches, limit concurrency to match available CPU and memory rather than launching unlimited image operations at once.

Common errors and fixes

“Input image exceeds overlay dimensions” or a missing watermark

Check the screenshot dimensions and the SVG or text-layer dimensions. An overlay must not be larger than the processed image. Reduce the SVG width and height, resize the base first, or calculate a smaller overlay for narrow screenshots.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Text is clipped

Increase the SVG canvas height and width, move the baseline inward, or set a sufficient width and height on generated text input. SVG text uses a baseline: a y value too close to the top or bottom can clip glyphs and accents.

The watermark is unreadable

Use a color with stronger contrast, increase or decrease opacity, add a translucent backing rectangle, or move the label to a less visually busy corner. A fixed white label is not reliably legible on arbitrary screenshots.

The font changes between machines

Install the same font on every host, or choose a broadly available fallback such as a generic sans-serif family. SVG and text rendering use the host’s font discovery configuration, so a font name in code does not install that font.

The output is unexpectedly huge or blurry

Choose the output encoder deliberately. PNG is lossless but can be large; JPEG and WebP trade some fidelity for size. Avoid repeatedly decoding and re-encoding the same image in separate steps when one Sharp pipeline can perform the work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rotation or cropping seems to use the wrong coordinates

Place rotate or extract before composite(). Composition coordinates refer to the processed base image, not the original file’s dimensions.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you still need to obtain the screenshot, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether it was billed.

After downloading the clean image, you can pass it through the Sharp code above. The API call itself can be made with cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

See the ScreenshotNeo documentation for parameters and response details. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. There is a free allowance of 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Practical design decisions

Pick a placement that does not hide content

Bottom-right is conventional, but browser screenshots often contain controls or cookie notices there. Inspect the target layout and use a corner with stable empty space. For automated jobs, keep the placement configurable.

Keep the watermark predictable

Use a fixed SVG canvas, explicit font size, and explicit opacity. If source screenshots vary greatly in width, generate the SVG dimensions from the source metadata or use a smaller design that fits the narrowest expected image.

Keep originals and verify output

Write to a separate filename or directory, check the returned promise for errors, and inspect the resulting dimensions and format when the files are consumed by another system. Do not overwrite originals until the output has passed your visual and automated checks.

Frequently Asked Questions

Can I watermark a screenshot without saving a separate overlay file?

Yes. Pass an SVG string or generated-text object as a buffer or input directly to Sharp’s composite() method.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which format should I use for a screenshot watermark?

Use PNG when crisp interface text and edges are the priority. Choose JPEG or WebP when a smaller file is more important and some compression is acceptable.

Can one Sharp call add multiple watermarks?

Yes. Provide multiple overlay objects in the array passed to composite(), such as a logo and a copyright label.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.