Chrome Extension Screen Annotation Tool: A Developer Guide
Screen annotation tools have become essential for developers, technical writers, and support teams. Whether you need to highlight a bug in a screenshot, create visual documentation, or explain a complex UI concept to stakeholders, a well-built Chrome extension can transform static screenshots into interactive, annotated visuals.
This guide covers the technical foundations of building a Chrome extension for screen annotation, from understanding the Chrome APIs you’ll need to implementing drawing, text overlay, and export functionality.
Core Architecture
A screen annotation extension typically consists of three main components:
- Content Script - Injected into web pages to capture screenshots and render annotations
- Background Script - Handles extension lifecycle, keyboard shortcuts, and messaging
- Popup/Options Page - User interface for configuring tools and preferences
The key Chrome APIs you’ll work with include chrome.tabs.captureVisibleTab(), chrome.downloads, chrome.storage, and the Canvas API for rendering.
Capturing Screenshots
The foundation of any annotation tool is screenshot capture. Chrome provides the chrome.tabs.captureVisibleTab() API for this purpose:
// background.js - Capture visible tab
async function captureTab(tabId) {
try {
const imageDataUrl = await chrome.tabs.captureVisibleTab(tabId, {
format: 'png',
quality: 100
});
return imageDataUrl;
} catch (error) {
console.error('Capture failed:', error);
throw error;
}
}
This returns a base64-encoded data URL that you can render directly onto a canvas element in your content script. The captureVisibleTab method captures only what’s currently visible in the viewport—not the entire scrollable page.
For full-page screenshots, you’ll need to capture multiple viewports and stitch them together, or use the more advanced Page Capture API with chrome.pageCapture.saveAsMHTML() for document-style captures.
Building the Annotation Canvas
Once you have the screenshot, the next step is creating an interactive canvas overlay. Here’s a practical implementation pattern:
// content-script.js - Setup annotation canvas
class AnnotationCanvas {
constructor(imageDataUrl) {
this.image = new Image();
this.image.src = imageDataUrl;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.annotations = [];
this.image.onload = () => {
this.canvas.width = this.image.width;
this.canvas.height = this.image.height;
this.ctx.drawImage(this.image, 0, 0);
this.setupEventListeners();
};
}
setupEventListeners() {
this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
this.canvas.addEventListener('mousemove', (e) => this.draw(e));
this.canvas.addEventListener('mouseup', () => this.stopDrawing());
}
// Additional methods for drawing tools
}
The canvas approach gives you pixel-level control over rendering. Each annotation (rectangle, arrow, text) gets stored as a data object, enabling features like undo/redo and layer management.
Drawing Tools Implementation
For a practical annotation tool, you’ll implement several drawing modes:
Rectangle Highlight - Useful for highlighting UI elements or error messages:
drawRectangle(startX, startY, endX, endY, color = '#ff0000', strokeWidth = 3) {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = strokeWidth;
this.ctx.strokeRect(startX, startY, endX - startX, endY - startY);
}
Arrow Tool - Ideal for pointing to specific elements:
drawArrow(fromX, fromY, toX, toY, color = '#ff0000') {
const headLength = 15;
const angle = Math.atan2(toY - fromY, toX - fromX);
this.ctx.beginPath();
this.ctx.moveTo(fromX, fromY);
this.ctx.lineTo(toX, toY);
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 3;
this.ctx.stroke();
// Arrow head
this.ctx.beginPath();
this.ctx.moveTo(toX, toY);
this.ctx.lineTo(
toX - headLength * Math.cos(angle - Math.PI / 6),
toY - headLength * Math.sin(angle - Math.PI / 6)
);
this.ctx.lineTo(
toX - headLength * Math.cos(angle + Math.PI / 6),
toY - headLength * Math.sin(angle + Math.PI / 6)
);
this.ctx.closePath();
this.ctx.fillStyle = color;
this.ctx.fill();
}
Text Overlay - For adding labels or descriptions:
drawText(x, y, text, fontSize = 16, color = '#ffffff', bgColor = '#000000') {
this.ctx.font = `${fontSize}px sans-serif`;
const metrics = this.ctx.measureText(text);
const padding = 4;
// Background
this.ctx.fillStyle = bgColor;
this.ctx.fillRect(
x - padding,
y - fontSize,
metrics.width + padding * 2,
fontSize + padding * 2
);
// Text
this.ctx.fillStyle = color;
this.ctx.fillText(text, x, y);
}
Managing Annotations
Storing annotations as structured data rather than rasterizing immediately provides flexibility:
class AnnotationManager {
constructor() {
this.annotations = [];
this.history = [];
this.historyIndex = -1;
}
addAnnotation(type, data) {
const annotation = {
id: Date.now(),
type,
data,
timestamp: new Date().toISOString()
};
this.annotations.push(annotation);
this.saveToHistory();
return annotation;
}
saveToHistory() {
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push(JSON.parse(JSON.stringify(this.annotations)));
this.historyIndex++;
}
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.annotations = JSON.parse(
JSON.stringify(this.history[this.historyIndex])
);
}
}
redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.annotations = JSON.parse(
JSON.stringify(this.history[this.historyIndex])
);
}
}
}
This approach lets users undo mistakes, edit existing annotations, or save annotation presets for common use cases.
Export Functionality
When users finish annotating, you need a way to export the result. The Downloads API handles this cleanly:
function exportCanvas(canvas, filename = 'screenshot-annotation.png') {
canvas.toBlob(async (blob) => {
const url = URL.createObjectURL(blob);
try {
const downloadId = await chrome.downloads.download({
url: url,
filename: filename,
saveAs: true
});
// Cleanup
setTimeout(() => URL.revokeObjectURL(url), 1000);
} catch (error) {
console.error('Export failed:', error);
}
}, 'image/png');
}
Keyboard Shortcuts and User Experience
Power users expect keyboard-driven workflows. Register global shortcuts in your manifest:
{
"commands": {
"capture-and-annotate": {
"suggested_key": "Ctrl+Shift+A",
"description": "Capture current tab and open annotation mode"
},
"quick-arrow": {
"suggested_key": "Ctrl+Shift+Arrow",
"description": "Quick arrow tool"
}
}
}
Handle these in your background script:
chrome.commands.onCommand.addListener((command) => {
if (command === 'capture-and-annotate') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
captureAndOpenAnnotation(tabs[0].id);
});
}
});
Performance Considerations
For extensions that handle frequent annotations, optimize your rendering:
- Layer separation - Keep the original screenshot on a bottom canvas layer and annotations on a separate layer to avoid re-rendering the base image
- RequestAnimationFrame - Use for smooth drawing updates during mouse movement
- Debounce storage - Don’t save to chrome.storage on every stroke; batch updates
Conclusion
Building a Chrome extension for screen annotation combines web platform APIs with canvas graphics programming. The key decisions involve choosing between immediate-mode rendering (simpler) versus retained-mode annotation objects (more features), handling cross-origin constraints, and designing intuitive keyboard shortcuts for power users.
The implementation patterns shown here provide a foundation you can extend with features like cloud sync, team sharing, or integration with documentation systems. Start with the core capture-and-annotate flow, then layer in advanced features based on your users’ workflows.
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