{"record":{"id":"008c34b7fd65be59","repo":"TryGhost/Ghost","slug":"clipboard-api-not-supported-in-this-browser","errorCode":null,"errorMessage":"Clipboard API not supported in this browser","messagePattern":"Clipboard API not supported in this browser","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/activitypub/src/utils/screenshot.ts","lineNumber":62,"sourceCode":"            allowTaint: true,\n            imageTimeout: 0,\n            onclone: (_document, clonedElement) => fixFirefoxTextSpacing(clonedElement)\n        });\n\n        await new Promise<void>((resolve, reject) => {\n            canvas.toBlob(async (blob) => {\n                if (!blob) {\n                    reject(new Error('Failed to create blob from canvas'));\n                    return;\n                }\n\n                try {\n                    if (copyToClipboard) {\n                        if (navigator.clipboard && 'write' in navigator.clipboard && typeof ClipboardItem !== 'undefined') {\n                            const clipboardItem = new ClipboardItem({'image/png': blob});\n                            await navigator.clipboard.write([clipboardItem]);\n                        } else {\n                            throw new Error('Clipboard API not supported in this browser');\n                        }\n                    } else {\n                        const url = URL.createObjectURL(blob);\n                        const link = document.createElement('a');\n                        link.href = url;\n                        link.download = filename;\n\n                        document.body.appendChild(link);\n                        link.click();\n\n                        document.body.removeChild(link);\n                        URL.revokeObjectURL(url);\n                    }\n\n                    resolve();\n                } catch (error) {\n                    reject(error);\n                }","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/activitypub/src/utils/screenshot.ts#L44-L80","documentation":"Thrown by captureScreenshot() when copyToClipboard:true is requested but the browser lacks the async Clipboard API (navigator.clipboard.write or the global ClipboardItem constructor). html2canvas has already succeeded and produced a PNG blob; only the clipboard-write step fails because the capability is absent. It reports a browser-capability gap, not a runtime defect.","triggerScenarios":"Calling captureScreenshot({copyToClipboard:true}) where navigator.clipboard is undefined (insecure context / plain HTTP, since the async Clipboard API requires a secure context), or where ClipboardItem is undefined (legacy browsers). Also fires when an extension or incognito mode blocks clipboard writes.","commonSituations":"Staging served over http:// (not HTTPS) where the secure-context clipboard API is unavailable; corporate-locked browsers that disable clipboard access; testing in an older Safari/Edge build that predates ClipboardItem; headless test runners without clipboard emulation.","solutions":["Feature-detect before calling: if (!(navigator.clipboard && 'write' in navigator.clipboard && typeof ClipboardItem !== 'undefined')), pass copyToClipboard:false so the function uses its download path instead.","Serve the admin over HTTPS or localhost (both are secure contexts) so navigator.clipboard is available.","Wrap the call in try/catch and, on failure, retry with copyToClipboard:false to deliver the screenshot as a file download."],"exampleFix":"// before\nawait captureScreenshot({copyToClipboard: true});\n\n// after\nconst canCopy = !!(navigator.clipboard && 'write' in navigator.clipboard && typeof ClipboardItem !== 'undefined');\ntry {\n    await captureScreenshot({copyToClipboard: canCopy});\n} catch (e) {\n    // clipboard unsupported — fall back to download\n    await captureScreenshot({copyToClipboard: false});\n}","handlingStrategy":"validation","validationCode":"// Run before calling captureScreenshot({copyToClipboard: true})\nexport function canCopyScreenshotToClipboard(): boolean {\n    return typeof navigator !== 'undefined'\n        && !!navigator.clipboard\n        && 'write' in navigator.clipboard\n        && typeof ClipboardItem !== 'undefined';\n}\n// usage: captureScreenshot({copyToClipboard: canCopyScreenshotToClipboard()})","typeGuard":null,"tryCatchPattern":"import {captureScreenshot} from '../utils/screenshot';\ntry {\n    await captureScreenshot({copyToClipboard: true});\n} catch (e) {\n    // capability missing — fall back to a plain file download\n    if (e instanceof Error && e.message.includes('Clipboard API not supported')) {\n        await captureScreenshot({copyToClipboard: false});\n    } else {\n        throw e;\n    }\n}","preventionTips":["Feature-detect navigator.clipboard.write + ClipboardItem before offering a 'copy to clipboard' button.","Serve the admin over a secure context (HTTPS or localhost) — the async Clipboard API is unavailable otherwise.","Always provide the download fallback UX so users on unsupported browsers still get the screenshot."],"tags":["clipboard","browser-support","secure-context","screenshot","activitypub"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}