Build a Screen Sharing Chrome Extension (2026)

Building a screen sharing Chrome extension requires understanding Chrome’s desktop capture APIs, permission models, and the constraints imposed by Manifest V3. This guide covers the technical implementation for developers and power users who want to integrate screen sharing into their browser-based workflows. from basic capture setup through production-ready architectures with recording, streaming, and annotation capabilities.

Understanding Chrome’s Desktop Capture API

Chrome provides the chrome.desktopCapture API for capturing screen, windows, and tabs. This API powers built-in features like Google Meet screen sharing and is the foundation for any screen sharing extension.

The API offers several capture sources:

  • screen: Entire display screens
  • window: Individual application windows
  • tab: Specific browser tabs
  • audio: System or tab audio (with limitations)
// Request screen capture permission
async function startScreenShare() {
 const sources = await chrome.desktopCapture.getDesktopSources({
 types: ['screen', 'window'],
 thumbnailSize: { width: 320, height: 180 }
 });
 // Display source picker UI to user
 return sources;
}

The getDesktopSources() method returns an array of DesktopCaptureSource objects, each containing an ID, name, thumbnail, and type. Your extension must present these to users for selection.

It is worth understanding the difference between the two main capture paths before writing any code. The chrome.desktopCapture API is available only in extension contexts and requires the desktopCapture permission in your manifest. The getDisplayMedia() API from the Screen Capture specification is available in regular web pages and does not require an extension at all. but it shows a browser-native picker that you cannot customize. Extensions that need custom source selection UI, silent background recording, or programmatic source selection must use the extension API path.

Manifest V3 Permission Requirements

Manifest V3 requires declaring specific permissions in your extension manifest:

{
 "manifest_version": 3,
 "name": "Screen Share Pro",
 "version": "1.0.0",
 "permissions": [
 "desktopCapture",
 "storage",
 "downloads"
 ],
 "host_permissions": [
 "<all_urls>"
 ],
 "background": {
 "service_worker": "background.js"
 },
 "action": {
 "default_popup": "popup.html"
 }
}

Note that desktopCapture is a permission, not a host permission. However, if your extension needs to send captured streams to specific domains, you’ll need appropriate host permissions for those URLs. The storage permission is necessary if you want to persist capture settings or session metadata between uses. The downloads permission is required if you use the chrome.downloads API to save recordings to disk.

One important Manifest V3 constraint: background service workers are ephemeral and can be terminated by Chrome at any time. For long-running capture sessions, you must use the service worker keep-alive pattern or move stream management to a content script that persists as long as the tab is open.

Building the Capture Flow

The typical screen sharing workflow involves requesting sources, obtaining a media stream, and processing or transmitting it:

// Complete capture workflow
async function captureTab(tabId) {
 // Step 1: Get stream from specific tab
 const stream = await navigator.mediaDevices.getUserMedia({
 audio: false,
 video: {
 mandatory: {
 chromeMediaSource: 'tab',
 chromeMediaSourceId: tabId
 }
 }
 });
 return stream;
}
// Capture with audio from system
async function captureWithAudio(sourceId) {
 const stream = await navigator.mediaDevices.getUserMedia({
 audio: {
 mandatory: {
 chromeMediaSource: 'desktop',
 chromeMediaSourceId: sourceId
 }
 },
 video: {
 mandatory: {
 chromeMediaSource: 'desktop',
 chromeMediaSourceId: sourceId
 }
 }
 });
 return stream;
}

The chromeMediaSource and chromeMediaSourceId constraints are Chrome-specific and allow targeting specific capture sources obtained through the desktopCapture API. These constraints use the legacy mandatory format rather than the modern ideal/exact format because Chrome’s capture pipeline still processes them through the older constraint system internally.

A complete source selection flow looks like this in practice:

// popup.js. show source picker, start capture on selection
async function showSourcePicker() {
 const sources = await chrome.desktopCapture.getDesktopSources({
 types: ['screen', 'window', 'tab'],
 thumbnailSize: { width: 320, height: 180 }
 });
 const container = document.getElementById('source-list');
 container.innerHTML = '';
 sources.forEach(source => {
 const item = document.createElement('div');
 item.className = 'source-item';
 const thumb = document.createElement('img');
 thumb.src = source.thumbnail.toDataURL();
 const label = document.createElement('span');
 label.textContent = source.name;
 item.appendChild(thumb);
 item.appendChild(label);
 item.addEventListener('click', () => beginCapture(source.id));
 container.appendChild(item);
 });
}
async function beginCapture(sourceId) {
 chrome.runtime.sendMessage({ action: 'startCapture', sourceId });
 window.close(); // Close popup after initiating
}

Handling Stream Constraints

Developers should understand the constraints available for different capture types:

const captureOptions = {
 video: {
 // Frame rate - reduce for lower bandwidth
 frameRate: 30,
 // Dimensions - match source or specify custom
 width: { ideal: 1920 },
 height: { ideal: 1080 },
 // Display surface type (for screen share)
 displaySurface: 'monitor' // 'monitor', 'window', 'browser'
 },
 audio: {
 // Echo cancellation helps with feedback
 echoCancellation: { ideal: true },
 // Noise suppression for clearer audio
 noiseSuppression: { ideal: true }
 }
};

For tab capture, the displaySurface constraint doesn’t apply. Screen capture supports monitor (entire screen), window (specific window), and browser (specific tab).

Here is how constraint choices affect practical outcomes:

Constraint Value Use Case
frameRate 60 Smooth animation demos, game capture
frameRate 15 Low-bandwidth situations, static content
width.ideal 1920 Full HD recording for detailed docs
width.ideal 1280 Standard quality, smaller file size
echoCancellation true Microphone mixed with screen audio
noiseSuppression false Music or system audio preservation

The ideal constraint modifier tells the browser to try for the specified value but accept alternatives. Use exact when you need a specific resolution and are prepared to handle the failure case if the source cannot meet it.

Security Considerations

Screen sharing extensions handle sensitive data, so security is critical:

User Consent: Always display clear UI showing what is being shared. Chrome automatically shows a system prompt, but your extension should reinforce this with its own indicator. typically a persistent icon or badge that shows capture is active.

Data Handling: Streams are MediaStream objects. You cannot access raw pixels directly. video frames are processed by the browser’s media pipeline. If you need to analyze frames, use the VideoFrame API or canvas capture:

// Capture frame to canvas for processing
function captureFrame(videoElement) {
 const canvas = document.createElement('canvas');
 canvas.width = videoElement.videoWidth;
 canvas.height = videoElement.videoHeight;
 const ctx = canvas.getContext('2d');
 ctx.drawImage(videoElement, 0, 0);
 return canvas.toDataURL('image/png');
}

Storage Permissions: If your extension saves recordings, use the File System Access API or chrome.downloads API with proper permission checks.

Credential Exposure: Frame analysis for bug reporting or documentation generation can inadvertently capture passwords, API keys, or authentication tokens visible on screen. If your extension performs any pixel-level analysis or screenshot capture, document this clearly in your privacy policy and consider implementing a pause mechanism that users can trigger before entering sensitive data.

Content Security Policy: Your extension’s CSP should be as restrictive as possible. Avoid unsafe-inline and unsafe-eval in your manifest CSP declaration. This protects against injected scripts that might try to exfiltrate captured streams.

Common Implementation Patterns

Recording to Local File

async function startRecording(stream) {
 const mediaRecorder = new MediaRecorder(stream, {
 mimeType: 'video/webm;codecs=vp9'
 });
 const chunks = [];
 mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
 mediaRecorder.onstop = () => {
 const blob = new Blob(chunks, { type: 'video/webm' });
 // Save or process the recording
 const url = URL.createObjectURL(blob);
 chrome.downloads.download({
 url,
 filename: `recording-${Date.now()}.webm`,
 saveAs: true
 });
 URL.revokeObjectURL(url);
 };
 mediaRecorder.start(1000); // Collect data every 1 second
 return mediaRecorder;
}

Requesting data every second via the timeslice argument to start() is important for long recordings. Without it, all data accumulates in memory until stop() is called. For recordings exceeding a few minutes, this will cause the tab to run out of memory. The timeslice approach lets you flush chunks to storage progressively.

MIME type availability varies by platform. VP9 in WebM is broadly supported on Chrome across platforms. VP8 is a reliable fallback. For users who need MP4 output, you will need server-side transcoding or a WASM-based tool like ffmpeg.wasm, since Chrome’s MediaRecorder does not natively support H.264 in MP4 containers on all platforms.

Streaming to WebRTC

For real-time screen sharing to a remote server:

async function streamToPeerConnection(stream, peerConnection) {
 stream.getTracks().forEach(track => {
 peerConnection.addTrack(track, stream);
 });
 const offer = await peerConnection.createOffer();
 await peerConnection.setLocalDescription(offer);
 return offer;
}

Handling Stream Termination

Users can stop sharing at any time from the browser’s built-in UI. the notification bar that appears when capture is active. Your extension must handle this gracefully:

function attachTerminationHandlers(stream, onStop) {
 const videoTrack = stream.getVideoTracks()[0];
 if (videoTrack) {
 videoTrack.addEventListener('ended', () => {
 console.log('Stream ended by user or system');
 onStop();
 });
 }
}

Without this handler, your extension may continue running recording logic against a dead stream, producing empty or corrupted output files.

Extension Architecture Recommendations

For production screen sharing extensions, structure your code with clear separation:

  • popup.js: Handles user-initiated capture requests and source selection UI
  • content-script.js: Manages in-page video elements for preview or annotation overlays
  • background.js: Coordinates capture sessions and maintains state across the extension lifecycle
  • offscreen.html + offscreen.js: Handles MediaRecorder processing (required in MV3 for DOM-dependent operations in background contexts)
  • utils/: Shared utilities for stream processing and storage management

Manifest V3 service workers cannot use MediaRecorder because it requires a DOM context. Chrome’s Offscreen Documents API was introduced specifically to address this:

// manifest.json. add offscreen permission
{
 "permissions": ["desktopCapture", "storage", "downloads", "offscreen"]
}
// background.js. create offscreen document for recording
async function ensureOffscreenDocument() {
 const existingContexts = await chrome.runtime.getContexts({
 contextTypes: ['OFFSCREEN_DOCUMENT']
 });
 if (existingContexts.length === 0) {
 await chrome.offscreen.createDocument({
 url: 'offscreen.html',
 reasons: ['USER_MEDIA'],
 justification: 'MediaRecorder requires DOM context for screen recording'
 });
 }
}

Use Chrome’s message passing API to communicate between components:

// From popup to background
chrome.runtime.sendMessage({
 action: 'startCapture',
 sourceId: selectedSource.id
}, response => {
 console.log('Capture started:', response.streamId);
});
// From background to offscreen document
chrome.runtime.sendMessage({
 action: 'startRecording',
 streamId: captureStreamId
});

Testing and Debugging

Debug screen sharing extensions using Chrome DevTools:

  1. Test with chrome://inspect/#extensions for background page debugging
  2. Use chrome://webrtc-internals for WebRTC stream inspection. this shows active peer connections, ICE candidates, codec negotiation, and real-time bandwidth statistics
  3. Check console for MediaStream errors
  4. Verify permissions in chrome://extensions
  5. Use the chrome://media-internals page to inspect active MediaStreams and their properties

Common issues include missing permissions, HTTPS requirement for getUserMedia, and incorrect MIME types for MediaRecorder.

A systematic approach to debugging capture failures:

Error Likely Cause Resolution
NotAllowedError Missing desktopCapture permission Add to manifest permissions
NotFoundError Source ID expired or invalid Re-fetch sources before capture
OverconstrainedError Resolution not supported Use ideal instead of exact
NotSupportedError MIME type unavailable Use MediaRecorder.isTypeSupported()
Stream tracks end immediately Tab closed during capture Check track state before recording

Use Cases for Developers

Screen sharing extensions serve various developer workflows:

  • Bug reporting: Capture reproduction steps automatically, annotate screenshots, and attach them to issue trackers via API
  • Documentation: Generate walkthrough videos for projects without leaving the browser
  • Code review: Share specific windows during pair programming with annotation overlays
  • Training: Create in-browser tutorials with step-by-step capture tied to click events
  • Remote collaboration: Integrate with custom communication tools that need capture capabilities not available in generic solutions
  • Automated testing: Use frame capture to visually verify UI state during end-to-end test runs
  • Accessibility audits: Record interaction sessions for review by accessibility specialists

Building your own extension gives you control over the capture experience, storage options, and integration points that off-the-shelf solutions may not provide. The investment in understanding Chrome’s capture APIs pays off whenever you need capture behavior that generic meeting tools or screen recorder products do not support. custom source selection, programmatic trigger points, silent background recording, or deep integration with your existing developer toolchain.


Try it: Paste your error into our Error Diagnostic for an instant fix.

I'm a solo developer in Vietnam. 50K Chrome extension users. $500K+ on Upwork. 5 Claude Max subscriptions running agent fleets in parallel. These are my actual CLAUDE.md templates, orchestration configs, and prompts. Not a course. Not theory. The files I copy into every project before I write a line of code. **[See what's inside →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-default&utm_campaign=screen-sharing-chrome-extension)** $99 once. Free forever. 47/500 founding spots left.

Related Reading

Built by theluckystrike. More at zovo.one

Set it up → Build your permission config with our Permission Configurator.