Last updated: March 15, 2026
Build a Chrome extension using the Canvas API to compress images before upload, reducing file sizes by 70-90% and dramatically speeding up upload times across all websites. Large image files create slow uploads, consume unnecessary bandwidth, and hit file size limits—a local browser solution avoids external services while keeping data private. This guide walks you through creating an extension that intercepts file inputs, compresses using Canvas, and replaces the original file with an optimized version that works across any website.
Why Client-Side Compression Matters
Uploading unoptimized images affects both performance and user experience. Large images take longer to upload, especially on slower connections. Many platforms impose strict file size limits—WordPress defaults to 2MB, email services often cap attachments at 25MB, and API endpoints may reject payloads exceeding certain thresholds.
Client-side compression using the Canvas API offers several advantages. The compression happens locally on the user’s device, meaning no server-side processing is required. This reduces bandwidth usage and upload times significantly. The entire process happens in the browser, keeping data private and eliminating the need for external compression services.
Setting Up Your Extension Structure
Every Chrome extension requires a manifest file and a background service worker. For this image compression extension, you’ll need:
/compress-before-upload
/manifest.json
/content.js
/background.js
/popup.html
/popup.js
/icon.png
The manifest defines permissions and registers the extension’s components. Your content script will handle detecting file input changes, while the background script manages communication between components.
Writing the Manifest
Create a manifest.json file with the necessary permissions:
{
"manifest_version": 3,
"name": "Compress Images Before Upload",
"version": "1.0",
"description": "Automatically compress images before uploading to any website",
"permissions": ["storage", "activeTab", "scripting"],
"host_permissions": ["<all_urls>"],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}]
}
Manifest V3 requires service workers instead of background pages. The host_permissions field grants access to all URLs, which is necessary for the extension to work across different websites.
Implementing the Compression Logic
The core compression happens in your content script. This script detects file input elements and monitors them for changes:
// content.js
class ImageCompressor {
constructor() {
this.quality = 0.7;
this.maxWidth = 1920;
this.maxHeight = 1920;
this.init();
}
init() {
this.observeFileInputs();
this.setupMutationObserver();
}
observeFileInputs() {
document.querySelectorAll('input[type="file"]').forEach(input => {
if (input.accept && input.accept.includes('image')) {
input.addEventListener('change', (e) => this.handleFileSelect(e));
}
});
}
setupMutationObserver() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const inputs = node.querySelectorAll ?
node.querySelectorAll('input[type="file"]') : [];
inputs.forEach(input => {
if (input.accept && input.accept.includes('image')) {
input.addEventListener('change', (e) => this.handleFileSelect(e));
}
});
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
async handleFileSelect(event) {
const input = event.target;
const files = Array.from(input.files);
for (const file of files) {
if (file.type.startsWith('image/')) {
const compressedFile = await this.compressImage(file);
this.replaceFile(input, compressedFile);
}
}
}
async compressImage(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
let { width, height } = img;
// Scale down if needed
if (width > this.maxWidth || height > this.maxHeight) {
const ratio = Math.min(
this.maxWidth / width,
this.maxHeight / height
);
width *= ratio;
height *= ratio;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
resolve(compressedFile);
},
'image/jpeg',
this.quality
);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
replaceFile(input, newFile) {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(newFile);
input.files = dataTransfer.files;
// Trigger change event for React/Angular form handling
input.dispatchEvent(new Event('change', { bubbles: true }));
}
}
new ImageCompressor();
This content script automatically attaches to file input elements that accept images. When a user selects files, it compresses each image using the Canvas API and replaces the original file with the compressed version.
Adding User Controls
Users should be able to adjust compression settings. Create a simple popup interface:
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 280px; padding: 16px; font-family: system-ui; }
label { display: block; margin-bottom: 8px; font-weight: 500; }
input[type="range"] { width: 100%; margin-bottom: 16px; }
.value-display { float: right; color: #666; }
.info { font-size: 12px; color: #666; margin-top: 12px; }
</style>
</head>
<body>
<h3>Image Compression</h3>
<label>
Quality: <span id="qualityValue" class="value-display">70%</span>
</label>
<input type="range" id="quality" min="0.1" max="1" step="0.1" value="0.7">
<label>
Max Width: <span id="widthValue" class="value-display">1920px</span>
</label>
<input type="range" id="maxWidth" min="800" max="4096" step="100" value="1920">
<label>
Max Height: <span id="heightValue" class="value-display">1920px</span>
</label>
<input type="range" id="maxHeight" min="600" max="4096" step="100" value="1920">
<p class="info">Changes apply to next file selection.</p>
<script src="popup.js"></script>
</body>
</html>
The popup script saves settings to Chrome storage:
// popup.js
document.addEventListener('DOMContentLoaded', () => {
// Load saved settings
chrome.storage.sync.get(['quality', 'maxWidth', 'maxHeight'], (settings) => {
if (settings.quality) {
document.getElementById('quality').value = settings.quality;
document.getElementById('qualityValue').textContent =
Math.round(settings.quality * 100) + '%';
}
if (settings.maxWidth) {
document.getElementById('maxWidth').value = settings.maxWidth;
document.getElementById('widthValue').textContent = settings.maxWidth + 'px';
}
if (settings.maxHeight) {
document.getElementById('maxHeight').value = settings.maxHeight;
document.getElementById('heightValue').textContent = settings.maxHeight + 'px';
}
});
// Save settings on change
document.getElementById('quality').addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
document.getElementById('qualityValue').textContent = Math.round(value * 100) + '%';
chrome.storage.sync.set({ quality: value });
});
document.getElementById('maxWidth').addEventListener('input', (e) => {
const value = parseInt(e.target.value);
document.getElementById('widthValue').textContent = value + 'px';
chrome.storage.sync.set({ maxWidth: value });
});
document.getElementById('maxHeight').addEventListener('input', (e) => {
const value = parseInt(e.target.value);
document.getElementById('heightValue').textContent = value + 'px';
chrome.storage.sync.set({ maxHeight: value });
});
});
Update the content script to read these settings from storage before compressing:
async loadSettings() {
return new Promise((resolve) => {
chrome.storage.sync.get(['quality', 'maxWidth', 'maxHeight'], (settings) => {
this.quality = settings.quality || 0.7;
this.maxWidth = settings.maxWidth || 1920;
this.maxHeight = settings.maxHeight || 1920;
resolve();
});
});
}
Testing Your Extension
Load your extension in Chrome by following these steps:
- Navigate to
chrome://extensions/ - Enable “Developer mode” in the top right corner
- Click “Load unpacked” and select your extension directory
- Visit any website with file upload functionality
- Select an image file and verify the compression works
The extension will automatically compress images when you select them through file input elements. You can adjust the quality settings using the extension popup.
Limitations and Considerations
This approach works well for most use cases but has some constraints. The Canvas API outputs JPEG format, so PNG images with transparency will lose their alpha channel. If transparency is essential, consider using WebP output or preserving the original format for PNG files.
Very large images might cause memory issues on lower-end devices. The extension includes dimension limits to help prevent this, but you can adjust these based on your typical use case.
Some web applications use custom file upload components that don’t use standard <input type="file"> elements. In these cases, you’ll need to extend the content script to handle their specific upload mechanisms.
Frequently Asked Questions
Who is this article written for?
This article is written for developers, technical professionals, and power users who want practical guidance. Whether you are evaluating options or implementing a solution, the information here focuses on real-world applicability rather than theoretical overviews.
How current is the information in this article?
We update articles regularly to reflect the latest changes. However, tools and platforms evolve quickly. Always verify specific feature availability and pricing directly on the official website before making purchasing decisions.
Are there free alternatives available?
Free alternatives exist for most tool categories, though they typically come with limitations on features, usage volume, or support. Open-source options can fill some gaps if you are willing to handle setup and maintenance yourself. Evaluate whether the time savings from a paid tool justify the cost for your situation.
How do I get started quickly?
Pick one tool from the options discussed and sign up for a free trial. Spend 30 minutes on a real task from your daily work rather than running through tutorials. Real usage reveals fit faster than feature comparisons.
What is the learning curve like?
Most tools discussed here can be used productively within a few hours. Mastering advanced features takes 1-2 weeks of regular use. Focus on the 20% of features that cover 80% of your needs first, then explore advanced capabilities as specific needs arise.