BlogScreenshots on your device
Chrome Extension Screen Capture
Record a Chrome tab, window, or screen with the right API, permissions, audio handling, and lifecycle fixes.
Direct answer: For a still image, use Chrome’s screenshot tools or a screenshot API. For video, use chrome.tabCapture for the active tab, or getDisplayMedia()/chrome.desktopCapture when the user must choose a tab, window, or monitor.
The API you choose controls capture scope, permissions, audio behavior, navigation handling, and whether Chrome displays a source picker.
1. Screenshot or video?
| Need | Use | Reason |
|---|---|---|
| One image | Chrome screenshot tools or an API | Produces PNG/JPEG/WebP without video encoding. |
| Tutorial or bug reproduction | Tab or display recording | Captures motion, clicks, and narration. |
| Only the active tab | chrome.tabCapture |
Narrow scope and extension-action start. |
| Window, monitor, or chosen tab | getDisplayMedia() or chrome.desktopCapture |
Uses a browser source picker. |
2. Chrome capture APIs
chrome.tabCapture
The tabCapture API captures the active tab after the extension is invoked. Capture persists through navigation in that tab until the tab closes or the stream is stopped. Chrome notes that obtaining a tab stream can stop local tab audio unless the extension routes audio back through an AudioContext.
getDisplayMedia()
Chrome’s extension guide describes getDisplayMedia() for a user-selected tab, window, or screen. The browser shows a picker and recording indicator. Content-script capture ends on navigation; Chrome recommends an offscreen document for background capture across navigation.
chrome.desktopCapture
The desktopCapture API requires the desktopCapture permission and returns a temporary, single-use stream ID. Cancelling returns an empty ID, and an unused ID expires after a few seconds.
3. Minimal Manifest V3 tab recorder
Create these files:
tab-recorder/\n manifest.json\n service-worker.js\n recorder.html\n recorder.js
Manifest
{\n \"manifest_version\": 3,\n \"name\": \"Local Tab Recorder\",\n \"version\": \"1.0.0\",\n \"permissions\": [\"tabCapture\", \"offscreen\", \"downloads\"],\n \"background\": {\"service_worker\": \"service-worker.js\"},\n \"action\": {\"default_title\": \"Record this tab\"}\n}
Request a stream ID
chrome.action.onClicked.addListener(async (tab) => {\n if (!tab.id) return;\n const streamId = await chrome.tabCapture.getMediaStreamId({ targetTabId: tab.id });\n await chrome.offscreen.createDocument({\n url: 'recorder.html',\n reasons: ['USER_MEDIA'],\n justification: 'Record the tab after the toolbar action.'\n }).catch(() => {});\n chrome.runtime.sendMessage({type: 'start-recording', streamId});\n});
Chrome documents this service-worker stream-ID approach for Chrome 116 and later. Check the API documentation if older versions must be supported.
Record and download WebM
let recorder;\nlet chunks = [];\n\nchrome.runtime.onMessage.addListener(async (message) => {\n if (message.type !== 'start-recording') return;\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: {mandatory: {chromeMediaSource: 'tab', chromeMediaSourceId: message.streamId}},\n video: {mandatory: {chromeMediaSource: 'tab', chromeMediaSourceId: message.streamId}}\n });\n chunks = [];\n recorder = new MediaRecorder(stream, {mimeType: 'video/webm'});\n recorder.ondataavailable = event => { if (event.data.size) chunks.push(event.data); };\n recorder.onstop = () => {\n const blob = new Blob(chunks, {type: 'video/webm'});\n const url = URL.createObjectURL(blob);\n chrome.downloads.download({url, filename: 'tab-recording.webm'});\n stream.getTracks().forEach(track => track.stop());\n };\n recorder.start();\n});\n\nfunction stopRecording() {\n if (recorder && recorder.state !== 'inactive') recorder.stop();\n}
Add a visible stop control, handle unsupported MIME types with MediaRecorder.isTypeSupported(), revoke object URLs after download, and close the offscreen document when complete.
4. Capture a chosen tab, window, or screen
async function recordChosenSource() {\n const stream = await navigator.mediaDevices.getDisplayMedia({video: true, audio: true});\n const recorder = new MediaRecorder(stream);\n const chunks = [];\n recorder.ondataavailable = e => e.data.size && chunks.push(e.data);\n recorder.onstop = () => {\n const blob = new Blob(chunks, {type: recorder.mimeType});\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = 'screen-recording.webm';\n a.click();\n };\n stream.getVideoTracks()[0].addEventListener('ended', () => recorder.stop());\n recorder.start();\n return () => recorder.stop();\n}
Call this from a visible click. Chrome can reject calls without a user gesture, and cancellation is a normal user outcome.
5. Permissions and privacy
The desktopCapture warning is Capture content of your screen.
Chrome’s Web Store help explains that permission warnings describe potential access and that optional permissions can be requested after installation.
- Install only extensions you trust and review their privacy policy.
- Request microphone or camera permissions only when needed.
- Do not record passwords, private messages, or confidential tabs.
- Redact secrets before sharing bug-report videos.
6. Navigation and audio limits
- Content-script recording ends on navigation. Use an offscreen document for background capture.
- Video capture changes focus and closes a popup; Chrome’s guide says popup capture is suitable only for audio.
- Tab audio, microphone audio, and system audio are separate tracks. Verify which tracks are requested.
- Listen for the video track’s
endedevent when the user stops sharing.
7. Comparing recorder extensions
| Question | Verify |
|---|---|
| Capture scope | Active tab, selected tab, window, or display. |
| Audio | Tab, microphone, system audio, or a mix. |
| Webcam | Overlay layout and camera permission. |
| Navigation | Whether recording survives page changes. |
| Output | Local download, hosted link, editor, and retention. |
| Limits | Current duration, storage, watermark, and team limits. |
Screencastify
Screencastify’s official page describes browser-tab, desktop, and webcam recording with editing and sharing. Verify current plan limits before choosing it.
Loom
Loom’s official page describes screen, camera, and audio recording, link sharing, and browser editing. Verify which features require its desktop app or a paid plan.
These are vendor descriptions, not independent benchmarks. Compare capture scope, audio, navigation, editing, sharing, and permissions.
8. Troubleshooting
| Symptom | Cause and fix |
|---|---|
Permission error from getDisplayMedia() |
Call it from a user click in a secure context; treat picker cancellation as an exit path. |
getMediaStreamId() fails |
Invoke from the extension action, include tabCapture, and confirm the tab ID. |
| Empty desktop-capture ID | The user cancelled. Stop cleanly and let them restart. |
| Stream ID expires | Consume it promptly and request a fresh ID for each recording. |
| Recording stops on navigation | Move the consumer from a content script to an offscreen document. |
| Tab audio is silent | Route captured audio through an AudioContext and verify requested tracks. |
| Popup closes | Start from the extension action and keep recording UI in a page or offscreen document. |
| File will not play | Check supported MIME types, flush data on stop, and stop all tracks. |
| Large files or memory use | Reduce resolution and duration; flush chunks periodically for long recordings. |
9. Performance, reliability, and cost
- Use tab capture when the job is tab-only.
- Keep recording state outside a short-lived popup.
- Persist source, MIME type, start time, and filename for diagnostics.
- Handle
ended,inactive, and download errors. - Long recordings should not keep every chunk in memory if your upload design can stream them.
- A browser extension needs no capture card, webcam, microphone, or external drive unless those are optional recording inputs.
10. Or skip the browser setup
For still screenshots and PDFs, ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one GET request. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Only clean shots are billed: bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server for Claude, Cursor, and other MCP clients with take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API docs for options.
cURL
curl -G 'https://api.screenshotneo.com/v1/shot' \\n -d access_key=YOUR_API_KEY \\n --data-urlencode url=https://stripe.com \\n -o shot.webp
Python
import requests\n\nr = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)\nr.raise_for_status()\nopen('shot.webp', 'wb').write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });\nconst res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);\nif (!res.ok) throw new Error(`HTTP ${res.status}`);\nconst fs = await import('node:fs/promises');\nawait fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS input, custom CSS and JavaScript, clicks, selector/delay/network-idle waits, request blocking, headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed links, async jobs with signed webhooks, bulk capture of 100 URLs per call, a usage API, and an OpenAPI spec. Familiar parameter names from other screenshot APIs also work.
Plans include 1,000 free shots per month with no card; paid plans start at $5 for 3,000 shots. Yearly billing gives two months free, and every feature is on every plan. Create a free ScreenshotNeo account.
11. FAQ
Can an extension record without user involvement?
The documented routes require an extension action or visible display picker, with recording indication.
Can I capture one DOM element?
Browser video APIs capture tabs, windows, or displays. Use a selector-capable screenshot API for a still element image.
What happens when the user changes tabs?
A tab stream remains tied to its target tab; a display stream remains tied to the selected source. Handle termination events.
Should recordings be uploaded?
Keep sensitive recordings local unless sharing is required. If uploading, document retention, access control, and deletion.
Is ScreenshotNeo a video recorder?
No. It creates still screenshots and PDFs from URLs; use Chrome capture APIs for motion and audio.


