NotAllowedError on clipboard.writeText — Permission or Gesture
Fix clipboard write failures by calling from a user gesture, requesting the clipboard-write permission in iframes, and handling rejection.
Symptoms
- DOMException: Failed to execute 'writeText' on 'Clipboard': Document is not focused
- Copy fails inside an iframe with NotAllowedError
- The copy works on click but not from a timer
Possible causes
- writeText is called outside a user gesture (such as after an await)
- The document is not focused or the code runs in an unfocused iframe
- A cross-origin iframe lacks the clipboard-write permission
Diagnosis steps
Reproduce and read the rejection
navigator.clipboard.writeText('x').catch(e => console.log(e.name, e.message))Interpretation
NotAllowedError names the cause: lost gesture, unfocused document, or missing permission.
Check permission and focus
console.log(document.hasFocus(), await navigator.permissions.query({ name: 'clipboard-write' }))Interpretation
Focus and permission state tell you which of the two conditions failed.
Root cause
The async clipboard API requires a user gesture, a focused document, and (in iframes) the clipboard-write permission. Losing any of these causes writeText to reject with NotAllowedError.
Fix
Call writeText synchronously within the gesture handler before any await, keep the document focused, request clipboard-write in the iframe allow attribute, and fall back to a legacy selection-based copy when it fails.
Risk note
A silent failure leaves users thinking the copy worked; always show success or failure feedback.
Verify the fix
Click copy and confirm the text lands on the clipboard; verify the iframe allow attribute permits clipboard-write.
Symptoms
The error “NotAllowedError on clipboard.writeText — Permission or Gesture” points at a specific failure mode. This guide documents the symptoms you can observe, the commands that isolate the cause, the root cause, the fix, and the checks that prove the fix works.
Observed symptoms include:
- DOMException: Failed to execute ‘writeText’ on ‘Clipboard’: Document is not focused
- Copy fails inside an iframe with NotAllowedError
- The copy works on click but not from a timer
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Reproduce and read the rejection
navigator.clipboard.writeText('x').catch(e => console.log(e.name, e.message))
NotAllowedError names the cause: lost gesture, unfocused document, or missing permission.
Step 2: Check permission and focus
console.log(document.hasFocus(), await navigator.permissions.query({ name: 'clipboard-write' }))
Focus and permission state tell you which of the two conditions failed.
Likely contributors, in order of frequency:
- writeText is called outside a user gesture (such as after an await)
- The document is not focused or the code runs in an unfocused iframe
- A cross-origin iframe lacks the clipboard-write permission
Root Cause
The async clipboard API requires a user gesture, a focused document, and (in iframes) the clipboard-write permission. Losing any of these causes writeText to reject with NotAllowedError.
The Fix
Call writeText synchronously within the gesture handler before any await, keep the document focused, request clipboard-write in the iframe allow attribute, and fall back to a legacy selection-based copy when it fails.
Watch out: A silent failure leaves users thinking the copy worked; always show success or failure feedback.
copyBtn.addEventListener('click', async () => {
try {
// inside the gesture: await the clipboard call immediately
await navigator.clipboard.writeText('text to copy');
showToast('Copied');
} catch (err) {
if (err.name === 'NotAllowedError') {
legacyCopy('text to copy'); // selection + execCommand fallback
} else {
console.error(err);
}
}
});
<!-- iframe must be permitted to use the clipboard -->
<iframe src="..." allow="clipboard-write"></iframe>
Verify and Prevent
Click copy and confirm the text lands on the clipboard; verify the iframe allow attribute permits clipboard-write.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.