API de Portapapeles de JavaScript
Aprende la API de Portapapeles asíncrona de JavaScript — copia y lee texto con navigator.clipboard, maneja datos enriquecidos con ClipboardItem y conoce los requisitos de contexto seguro y permisos.
La moderna API de Portapapeles, expuesta a través de navigator.clipboard, es la forma asíncrona y basada en promesas de leer y escribir en el portapapeles del sistema. Reemplaza el antiguo enfoque sincrónico de document.execCommand('copy') con métodos que devuelven promesas, por lo que se combinan de manera natural con async/await. La API es distinta de — pero a menudo se usa junto con — los eventos de portapapeles cut, copy y paste: los eventos te permiten interceptar lo que hace el usuario, mientras que navigator.clipboard permite que tu código inicie acciones de portapapeles directamente.
Escribir texto en el portapapeles
La tarea más común es copiar texto. navigator.clipboard.writeText(text) acepta un string, lo escribe en el portapapeles y devuelve una Promise que se resuelve cuando la escritura tiene éxito y se rechaza cuando falla.
Como devuelve una promesa, la forma más limpia de usarlo es dentro de una función async con try...catch, para poder dar retroalimentación al usuario en ambos casos:
<button id="copyBtn">Copy</button>
<span id="status"></span>
<script>
const button = document.getElementById('copyBtn');
const status = document.getElementById('status');
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText('Hello from W3docs!');
status.textContent = 'Copied!';
} catch (err) {
status.textContent = 'Copy failed';
console.error('Clipboard write failed:', err);
}
});
</script>El await pausa la ejecución hasta que la escritura en el portapapeles se establece. Si el usuario ha denegado el permiso o la página no está en un contexto seguro, la promesa se rechaza y el bloque catch se ejecuta — por eso nunca debes asumir que la copia tuvo éxito.
Leer texto del portapapeles
Para leer el texto actual del portapapeles, llama a navigator.clipboard.readText(). También devuelve una promesa, esta vez resolviéndose con el contenido de texto del portapapeles:
<button id="pasteBtn">Read clipboard</button>
<p id="output"></p>
<script>
const button = document.getElementById('pasteBtn');
const output = document.getElementById('output');
button.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
output.textContent = `Clipboard contains: ${text}`;
} catch (err) {
output.textContent = 'Could not read clipboard';
console.error('Clipboard read failed:', err);
}
});
</script>Leer es mucho más sensible que escribir, porque expone lo que el usuario ha copiado — posiblemente una contraseña u otros datos privados. Por esa razón, los navegadores protegen readText() de manera más estricta: requiere un gesto explícito del usuario, y algunos navegadores muestran una solicitud de permiso la primera vez, o solo permiten lecturas cuando la pestaña de la página está enfocada.
Requisitos y consideraciones
La API de Portapapeles tiene varias reglas que, si se ignoran, generan rechazos silenciosos. Ten estos puntos en mente cada vez que la utilices.
La API de Portapapeles solo funciona en un contexto seguro — es decir, HTTPS, o localhost durante el desarrollo. En una página con http:// sin cifrar, navigator.clipboard suele ser undefined. Las llamadas también generalmente requieren un gesto del usuario como un clic o una pulsación de tecla, por lo que activarlas al cargar la página fallará. La API de Permisos gobierna los permisos clipboard-read y clipboard-write, y las lecturas pueden solicitar confirmación al usuario. Dado que cualquier llamada puede rechazarse — permiso denegado, documento sin foco o navegador no compatible — envuelve siempre las llamadas al portapapeles en try...catch.
La página también necesita tener el foco para muchas operaciones del portapapeles. Si llamas a readText() desde, por ejemplo, un setTimeout mientras el usuario ha cambiado a otra pestaña, espera un rechazo con un error de "el documento no tiene foco". Ten en cuenta también que la lectura del portapapeles solo ocurre después de que el usuario haya enfocado e interactuado con tu página.
Copiar datos enriquecidos con ClipboardItem
El texto es el caso sencillo. Para copiar datos que no son texto — imágenes, HTML o varios formatos a la vez — usa navigator.clipboard.write(), que recibe un array de objetos ClipboardItem. Cada ClipboardItem asigna tipos MIME a sus datos (normalmente un Blob).
El siguiente ejemplo obtiene una imagen, envuelve el Blob resultante en un ClipboardItem y lo copia:
async function copyImage(url) {
try {
const response = await fetch(url);
const blob = await response.blob();
const item = new ClipboardItem({ [blob.type]: blob });
await navigator.clipboard.write([item]);
console.log('Image copied to clipboard');
} catch (err) {
console.error('Failed to copy image:', err);
}
}La clave dentro del constructor de ClipboardItem es el tipo MIME (aquí blob.type, p. ej. 'image/png'), y el valor son los datos para ese tipo. Un solo elemento puede contener múltiples representaciones — por ejemplo, tanto 'text/plain' como 'text/html' — lo que permite a la aplicación de destino elegir la mejor opción.
Leer datos enriquecidos sigue el mismo patrón con navigator.clipboard.read(), que se resuelve en un array de objetos ClipboardItem que inspeccionas por tipo:
async function readClipboardItems() {
const items = await navigator.clipboard.read();
for (const item of items) {
for (const type of item.types) {
const blob = await item.getType(type);
console.log(`Found ${type}`, blob);
}
}
}La compatibilidad de los navegadores con los métodos de datos enriquecidos (write y read con ClipboardItem) es más limitada e inconsistente que con los métodos de texto. Los tipos MIME de imagen varían especialmente según el navegador — image/png es el más fiable. Detecta la disponibilidad con if ('write' in navigator.clipboard) y recurre a copiar texto o una URL cuando los datos enriquecidos no estén disponibles.
El método alternativo heredado
Antes de la API asíncrona, copiar implicaba seleccionar un elemento y llamar a document.execCommand('copy'). Ese método está ahora obsoleto, pero sigue funcionando en navegadores más antiguos, por lo que es útil únicamente como alternativa. El patrón típico seleccionaba un <textarea> oculto y luego ejecutaba el comando:
function copyTextFallback(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy'); // deprecated
document.body.removeChild(textarea);
}El código moderno debe preferir la API asíncrona y recurrir al método alternativo solo cuando navigator.clipboard no esté disponible:
async function copyText(text) {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text);
} else {
copyTextFallback(text);
}
}Usos en el mundo real
La API de Portapapeles potencia muchas interacciones pequeñas pero valiosas que ves cada día:
- Botones "Copiar código" en páginas de documentación, para que los lectores puedan obtener un fragmento sin seleccionarlo manualmente.
- Botones "Copiar enlace para compartir" que colocan una URL en el portapapeles para pegarla en un chat o correo electrónico.
- Copiar resultados generados, como una contraseña, una clave de API o una cita formateada.
Sea lo que sea que copies, proporciona retroalimentación visual. Un botón que copia en silencio deja a los usuarios sin saber si funcionó. Cambia la etiqueta a "¡Copiado!", muestra un breve aviso emergente o actualiza un elemento de estado adyacente — esto también es una mejora de accesibilidad, ya que los usuarios de lectores de pantalla no reciben ninguna señal del portapapeles por parte del propio navegador.