v145 · Web APIs · Activation & Permissions
Activation & Permissions
The clipboardchange event has two distinct gating requirements in Chrome 145: the page must have sticky activation (a recent user gesture), and reading the new clipboard value requires the clipboard-read permission. Understanding the separation between "event fires" and "value readable" is key to building correct clipboard-aware UIs.
clipboardchange event fires to signal that a change occurred. Accessing the new value is a second step, gated by permission. This separation lets apps react to clipboard changes (show a "Paste" badge) without needing the full clipboard-read permission just to know something changed.
the two gates
Gate 1 — Sticky Activation
Controls whether the event fires at all. The page must have received a recent user gesture (click, keypress, touch). This prevents silent background surveillance from pages opened in background tabs and never interacted with.
A page without sticky activation will not receive clipboardchange events regardless of permission state.
Gate 2 — clipboard-read Permission
Controls whether the handler can read the clipboard value. The event fires with or without this permission, but navigator.clipboard.readText() inside the handler throws NotAllowedError if permission is denied.
When permission is prompt, the browser can show a permission dialog inside the handler because the event counts as a trusted user-gesture context.
what you get at each state
| Sticky activation | clipboard-read | Event fires? | Can read value? | Typical use |
|---|---|---|---|---|
| ✓ active | granted | Yes | Yes (silent) | Rich editor with full clipboard sync |
| ✓ active | prompt | Yes | Yes (prompts once) | On-demand paste preview — prompt on first use |
| ✓ active | denied | Yes | No | Show "clipboard changed" badge; manual paste only |
| ✗ inactive | any | No | No | Background tab — no surveillance possible |
correct handler pattern
- Attach the listener at page load (or when clipboard-related UI becomes active). No permission needed just to listen.
- When the event fires, query permission state before attempting
readText(). A direct permission query is fast and non-blocking. - If
granted: callreadText()silently and update UI. - If
prompt: callreadText()inside the handler — the browser can show the permission dialog here because the event fires in a trusted context. Wrap in try/catch in case the user denies. - If
denied: fall back gracefully — show a "Paste" button that lets the user manually Ctrl+V, or show a generic "clipboard updated" indicator without the value.
window.addEventListener('clipboardchange', async () => {
const { state } = await navigator.permissions.query({ name: 'clipboard-read' });
if (state === 'granted') {
const text = await navigator.clipboard.readText();
updatePastePreview(text);
} else if (state === 'prompt') {
try {
// Browser may show permission UI here — we're in a trusted event
const text = await navigator.clipboard.readText();
updatePastePreview(text);
} catch {
showManualPasteButton();
}
} else {
// 'denied' — still useful to know something changed
showClipboardChangedBadge();
}
});