How to Add Screenshot Functionality to a Chrome Extension

Capture visible tabs and full pages from inside your Chrome extension using Manifest V3 and the chrome.tabs API.

📋 Table of Contents

Setting Up Manifest V3

Chrome extensions in 2026 must use Manifest V3. To capture screenshots, your extension needs the activeTab permission (for capturing the current tab) and optionally tabs for broader access.

// manifest.json
{
  "manifest_version": 3,
  "name": "My Screenshot Extension",
  "version": "1.0",
  "description": "Capture screenshots of any webpage",
  "permissions": [
    "activeTab"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  }
}

Capturing the Visible Tab

The simplest way to capture a screenshot in a Chrome extension is using the chrome.tabs.captureVisibleTab() API. This captures exactly what the user sees in their browser viewport as a base64-encoded PNG or JPEG data URL.

// background.js (service worker)
chrome.action.onClicked.addListener(async (tab) => {
  try {
    const dataUrl = await chrome.tabs.captureVisibleTab(
      tab.windowId,
      { format: 'png', quality: 100 }
    );

    // Open the screenshot in a new tab
    chrome.tabs.create({ url: dataUrl });

    // Or download it directly
    chrome.downloads.download({
      url: dataUrl,
      filename: `screenshot-${Date.now()}.png`
    });
  } catch (error) {
    console.error('Screenshot failed:', error);
  }
});

Limitations: This method only captures the visible portion of the page (the viewport). It cannot capture content below the fold, and it doesn't work on chrome:// pages, the Chrome Web Store, or other restricted URLs.

Full-Page Screenshots: The Hard Part

Capturing the entire scrollable page from a Chrome extension is significantly more complex. There is no built-in captureFullPage() API. The common workaround involves:

  1. Injecting a content script into the page
  2. Programmatically scrolling the page section by section
  3. Capturing each viewport with captureVisibleTab()
  4. Stitching the resulting images together on an offscreen canvas
// content-script.js — Scroll-and-stitch approach (simplified)
async function captureFullPage() {
  const totalHeight = document.body.scrollHeight;
  const viewportHeight = window.innerHeight;
  const captures = [];

  for (let y = 0; y < totalHeight; y += viewportHeight) {
    window.scrollTo(0, y);
    // Wait for rendering to settle
    await new Promise(r => setTimeout(r, 300));

    // Request the background script to capture this viewport
    const dataUrl = await chrome.runtime.sendMessage({ action: 'capture' });
    captures.push({ dataUrl, y });
  }

  // Scroll back to the top
  window.scrollTo(0, 0);
  return captures; // Stitch these in an offscreen canvas
}

This approach is fragile. Sticky headers, position-fixed elements, and CSS animations can cause visual artifacts like duplicated navbars or misaligned sections between stitched segments.

Using a Screenshot API for Advanced Captures

For reliable full-page captures, element-specific captures, or dark-mode screenshots, the cleanest solution is to call a Screenshot API from your extension's background service worker. This avoids all the scrolling/stitching complexity.

// background.js — Using SnapAPI for full-page captures
chrome.action.onClicked.addListener(async (tab) => {
  const apiUrl = new URL('https://snapapi.alterra-solutions.com/v1/screenshot');
  apiUrl.searchParams.append('url', tab.url);
  apiUrl.searchParams.append('full_page', 'true');
  apiUrl.searchParams.append('format', 'png');
  apiUrl.searchParams.append('api_key', 'YOUR_API_KEY');

  try {
    const response = await fetch(apiUrl.toString());
    const blob = await response.blob();
    const dataUrl = await blobToDataUrl(blob);

    // Download the full-page screenshot
    chrome.downloads.download({
      url: dataUrl,
      filename: `fullpage-${Date.now()}.png`
    });
  } catch (error) {
    console.error('API screenshot failed:', error);
  }
});

function blobToDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

This approach gives you pixel-perfect full-page screenshots without any scrolling hacks, works on sites with complex sticky headers, and even lets you add parameters like dark_mode=true or device=mobile for advanced captures.

Power Your Chrome Extension with SnapAPI

100 free screenshots per month. Add full-page capture to your extension in minutes.

Get Started Free →