Building a Chrome extension that adds watermarks to images opens up practical possibilities for content creators, photographers, and developers working with visual assets. This guide walks you through the technical implementation, from understanding the core APIs to handling real-world use cases like batch processing and customizable watermark positioning.
Understanding the Core Challenge
Browser-based image manipulation requires working with the Canvas API, which provides powerful methods for drawing images and text. The key challenge lies in efficiently loading images from various sources—webpages, local files, or drag-and-drop inputs—and applying watermarks without degrading image quality.
Chrome extensions can access images through multiple pathways: the active tab’s DOM, user-uploaded files via the File System Access API, or clipboard data. Each pathway presents unique considerations for error handling and performance optimization.
Project Structure
A functional image watermarking extension requires these core components:
{
"manifest_version": 3,
"name": "Image Watermark Pro",
"version": "1.0",
"permissions": ["activeTab", "scripting", "downloads"],
"action": {
"default_popup": "popup.html"
}
}
The manifest declares the extension’s capabilities. Version 3 requires explicit permission declarations, improving security posture compared to earlier versions.
Implementing the Watermark Engine
The core functionality lives in a background script or popup that handles image processing. Here’s a practical implementation using canvas manipulation:
class ImageWatermarker {
constructor() {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
}
async loadImage(source) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = source;
});
}
applyWatermark(imageSource, options) {
const { text, position, opacity, fontSize } = options;
const img = await this.loadImage(imageSource);
this.canvas.width = img.width;
this.canvas.height = img.height;
this.ctx.drawImage(img, 0, 0);
this.ctx.globalAlpha = opacity;
this.ctx.font = `${fontSize}px Arial`;
this.ctx.fillStyle = 'white';
const metrics = this.ctx.measureText(text);
const pos = this.calculatePosition(position, img, metrics);
// Add shadow for better visibility
this.ctx.shadowColor = 'rgba(0,0,0,0.5)';
this.ctx.shadowBlur = 4;
this.ctx.fillText(text, pos.x, pos.y);
return this.canvas.toDataURL('image/png');
}
calculatePosition(position, img, metrics) {
const positions = {
'bottom-right': { x: img.width - metrics.width - 20, y: img.height - 20 },
'bottom-left': { x: 20, y: img.height - 20 },
'top-right': { x: img.width - metrics.width - 20, y: metrics.actualBoundingBoxAscent + 20 },
'top-left': { x: 20, y: metrics.actualBoundingBoxAscent + 20 },
'center': { x: (img.width - metrics.width) / 2, y: img.height / 2 }
};
return positions[position] || positions['bottom-right'];
}
}
This class demonstrates the fundamental approach: load an image onto a canvas, configure text rendering parameters, calculate positioning based on predefined locations, and export the result as a data URL.
Handling Tab Images
A common use case involves watermarking images directly from the active webpage. The Chrome Scripting API enables this:
async function watermarkTabImages(tabId, watermarkText) {
// Inject content script to find all images
const results = await chrome.scripting.executeScript({
target: { tabId },
func: () => {
const images = document.querySelectorAll('img');
return Array.from(images).map(img => ({
src: img.src,
width: img.naturalWidth,
height: img.naturalHeight
}));
}
});
const watermarker = new ImageWatermarker();
const processedImages = [];
for (const imgData of results[0].result) {
if (imgData.width > 100 && imgData.height > 100) { // Filter small icons
try {
const watermarked = await watermarker.applyWatermark(imgData.src, {
text: watermarkText,
position: 'bottom-right',
opacity: 0.7,
fontSize: Math.max(16, imgData.width / 20)
});
processedImages.push({ original: imgData.src, watermarked });
} catch (e) {
console.error('Failed to process image:', e);
}
}
}
return processedImages;
}
This script scans the active tab for images above a certain threshold, applies watermarks, and returns both original and processed versions. The font size scales proportionally with image dimensions, ensuring readability across different image sizes.
Batch Processing Implementation
For users who need to watermark multiple images simultaneously, implement batch processing with progress tracking:
async function batchWatermark(files, watermarkConfig) {
const results = [];
const watermarker = new ImageWatermarker();
for (let i = 0; i < files.length; i++) {
const file = files[i];
const reader = new FileReader();
const processed = await new Promise((resolve) => {
reader.onload = async (e) => {
const watermarked = await watermarker.applyWatermark(e.target.result, watermarkConfig);
resolve({ name: file.name, data: watermarked, index: i });
};
reader.readAsDataURL(file);
});
results.push(processed);
// Report progress to extension popup
chrome.runtime.sendMessage({
type: 'progress',
current: i + 1,
total: files.length
});
}
return results;
}
The progress reporting enables UI updates, keeping users informed during potentially lengthy batch operations.
Building the User Interface
The popup interface should provide intuitive controls for watermark customization:
<input type="text" id="watermarkText" placeholder="Enter watermark text">
<select id="position">
<option value="bottom-right">Bottom Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="top-right">Top Right</option>
<option value="top-left">Top Left</option>
<option value="center">Center</option>
</select>
<input type="range" id="opacity" min="0.1" max="1" step="0.1" value="0.7">
<input type="number" id="fontSize" value="24" min="8" max="200">
<button id="processBtn">Apply Watermark</button>
Connect these elements to the watermarking logic through event listeners in your popup script.
Performance Considerations
Image processing in the browser can be memory-intensive. Implement these optimizations:
- Process sequentially: Rather than parallel processing, handle images one at a time to prevent memory spikes
- Use appropriate formats: When possible, work with the original format and only convert on export
- Revoke object URLs: If using URL.createObjectURL, always revoke when done to prevent memory leaks
- Web Workers: For intensive processing, move watermarking logic to a Web Worker to keep the UI responsive
Extension Distribution
When publishing to the Chrome Web Store, ensure your extension handles the review process requirements:
- Clearly describe all permissions in the store listing
- Provide a working demo or clear documentation
- Handle cross-origin images gracefully, as some servers may block canvas export
- Consider offering a free tier with limited features, with paid upgrades for batch processing
Related Reading
- Claude Code for Beginners: Complete Getting Started Guide
- Best Claude Skills for Developers in 2026
- Claude Skills Guides Hub
Built by theluckystrike — More at zovo.one