Desarrollar extensiones de Chrome con Claude Code: MV3, mensajes y permisos mínimos
Crea una extensión Chrome MV3 con Claude Code: manifest, service worker, content script, permisos, storage y pruebas.
Empieza por el límite de la extensión
Una extensión de Chrome parece pequeña, pero funciona como un producto completo: manifest.json, Manifest V3, service worker, content script, mensajería, permisos, almacenamiento, revisión de seguridad, empaquetado y explicación para Chrome Web Store. Si le pides a Claude Code “crea una extensión de Chrome” sin restricciones, puede generar un scaffold grande con <all_urls>, tabs, popup, React, Vite y scripts externos que no necesitas en la primera versión.
En esta guía construiremos algo más controlado: una extensión Manifest V3 que resalta texto seleccionado en https://example.com/* desde un menú contextual. Usaremos JavaScript plano, chrome.storage.local y mensajes entre el service worker y el content script. Evitaremos permisos amplios, código remoto y dependencias innecesarias. El objetivo no es impresionar con una UI, sino tener una base que se pueda copiar, revisar y explicar.
Algunos términos conviene aclararlos. Manifest V3 es el formato actual del manifiesto de extensiones de Chrome. El service worker es el proceso de fondo que despierta ante eventos y no vive para siempre. El content script es el código que se inyecta en una página y puede leer o modificar el DOM. Message passing es la comunicación por mensajes JSON entre contextos de la extensión. Si ves la palabra harness en desarrollo con agentes, aquí significa “andamiaje” o base de trabajo para que el agente actúe con seguridad.
Consulta siempre fuentes oficiales: Chrome Extensions Manifest, About extension service workers, Chrome Content scripts, MDN Content scripts, Chrome Declare permissions y MDN permissions. Para mejorar los prompts, combina esta lectura con consejos de productividad de Claude Code.
Tres casos de uso reales
El primer caso es revisar documentación interna. La extensión puede limitarse a https://docs.example.com/* y resaltar términos de producto mientras se revisan notas de lanzamiento. Como el patrón de URL es estrecho, la explicación de permisos es clara.
El segundo caso es soporte al cliente. Un agente puede seleccionar un número de pedido en una consola interna y usar el menú contextual para resaltarlo o copiarlo. Aquí hay datos sensibles, por lo que debes pedir a Claude Code una tabla de datos leídos, guardados, enviados y excluidos.
El tercer caso es QA editorial y SEO. Un content script puede revisar longitud de description, número de h2, enlaces internos y enlaces oficiales externos en una página de borrador. Esa revisión encaja bien con controles CLI como los de desarrollo de herramientas CLI con Claude Code y con prácticas de seguridad en Claude Code.
Prompt recomendado para Claude Code
El prompt debe incluir la función, las prohibiciones y la verificación. En MV3 es importante recordar que el service worker no es persistente y que los permisos deben ser mínimos.
Crea una extensión Chrome Manifest V3 de ejemplo.
Requisitos:
- Limitar el objetivo a https://example.com/*
- Añadir un menú contextual para resaltar texto seleccionado
- Usar JavaScript plano: manifest.json, service-worker.js, content-script.js
- Guardar enabled y color en chrome.storage.local
- Comunicar service worker y content script mediante runtime messages
- No usar <all_urls>, tabs, eval, scripts CDN externos ni código remoto
- Añadir un smoke script con Playwright para confirmar que la extensión carga
- Incluir una tabla de revisión de permisos basada en la documentación de Chrome
La clave es no dejar que el ejemplo crezca demasiado pronto. Si Claude Code añade popup, options page, iconos, bundler y permisos amplios, pídele que reduzca la muestra antes de revisar el comportamiento.
Estructura y Manifest
La carpeta puede cargarse directamente con “Load unpacked” en chrome://extensions.
mv3-highlighter/
manifest.json
service-worker.js
content-script.js
package.json
playwright-extension-smoke.mjs
{
"manifest_version": 3,
"name": "Claude Code MV3 Highlighter",
"version": "0.1.0",
"description": "Highlights selected text on example.com from a context menu.",
"action": {
"default_title": "MV3 Highlighter"
},
"permissions": ["contextMenus", "storage"],
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": ["content-script.js"],
"run_at": "document_idle"
}
]
}
| Área | Configuración | Motivo | Evitado |
|---|---|---|---|
| Permisos API | contextMenus, storage | Menú y ajustes | tabs, scripting, downloads |
| Páginas | https://example.com/* | Un dominio de demo | <all_urls> |
| Host permissions | Ninguno | Basta con el content script estático | Permisos amplios |
| Código remoto | Ninguno | Revisión MV3 más simple | CDN, eval |
Service worker
El service worker recibe eventos y envía mensajes al content script. Como puede detenerse entre eventos, no guardes estado importante solo en memoria.
const MENU_ID = "highlight-selection";
const DEFAULT_SETTINGS = {
enabled: true,
color: "#fff176"
};
async function readSettings() {
return chrome.storage.local.get(DEFAULT_SETTINGS);
}
chrome.runtime.onInstalled.addListener(async () => {
const settings = await readSettings();
await chrome.storage.local.set(settings);
chrome.contextMenus.create({
id: MENU_ID,
title: "Highlight selected text",
contexts: ["selection"],
documentUrlPatterns: ["https://example.com/*"]
});
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId !== MENU_ID || !tab?.id || !info.selectionText) {
return;
}
const settings = await readSettings();
await chrome.tabs.sendMessage(tab.id, {
type: "HIGHLIGHT_SELECTION",
text: info.selectionText.slice(0, 120),
enabled: settings.enabled,
color: settings.color
});
});
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.type === "GET_SETTINGS") {
readSettings().then(sendResponse);
return true;
}
if (message?.type === "SAVE_SETTINGS") {
const nextSettings = {
enabled: Boolean(message.enabled),
color: String(message.color || DEFAULT_SETTINGS.color)
};
chrome.storage.local.set(nextSettings).then(() => {
sendResponse({ ok: true, settings: nextSettings });
});
return true;
}
return false;
});
El fallo típico es olvidar return true cuando sendResponse se llama después de una promesa. Revisa esta parte con Chrome Message passing.
Content script
El content script toca el DOM de la página, pero no debe guardar secretos ni tokens. Recibe un mensaje, resalta texto y deja una marca data-* para que Playwright confirme que fue inyectado.
const HIGHLIGHT_CLASS = "cc-mv3-highlight";
document.documentElement.dataset.ccMv3Highlighter = "ready";
function shouldSkipNode(node) {
const parent = node.parentElement;
if (!parent) return true;
return Boolean(
parent.closest(
`script, style, textarea, input, [contenteditable="true"], .${HIGHLIGHT_CLASS}`
)
);
}
function clearHighlights() {
for (const mark of document.querySelectorAll(`.${HIGHLIGHT_CLASS}`)) {
const text = document.createTextNode(mark.textContent || "");
mark.replaceWith(text);
text.parentElement?.normalize();
}
}
function createMark(text, color) {
const mark = document.createElement("mark");
mark.className = HIGHLIGHT_CLASS;
mark.textContent = text;
mark.style.backgroundColor = color;
mark.style.color = "#111";
mark.style.padding = "0 2px";
return mark;
}
function highlightText(term, color) {
const query = term.trim();
if (!query) return 0;
clearHighlights();
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
if (shouldSkipNode(node)) return NodeFilter.FILTER_REJECT;
return node.nodeValue.toLowerCase().includes(query.toLowerCase())
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
}
});
const matches = [];
while (walker.nextNode()) {
const node = walker.currentNode;
const index = node.nodeValue.toLowerCase().indexOf(query.toLowerCase());
if (index >= 0) matches.push({ node, index });
}
for (const { node, index } of matches.reverse()) {
const selected = node.splitText(index);
selected.splitText(query.length);
selected.replaceWith(createMark(selected.nodeValue, color));
}
return matches.length;
}
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.type !== "HIGHLIGHT_SELECTION") {
return false;
}
if (!message.enabled) {
clearHighlights();
sendResponse({ ok: true, count: 0 });
return false;
}
const count = highlightText(String(message.text || ""), message.color || "#fff176");
sendResponse({ ok: true, count });
return false;
});
Este resaltador es intencionalmente simple. Solo marca la primera coincidencia de cada text node y evita formularios, scripts, estilos y zonas editables. Para producción, añade pruebas de múltiples coincidencias, Shadow DOM, contenido dinámico y conflicto con marcas existentes.
Pruebas con Playwright y checklist manual
Playwright puede cargar una extensión sin empaquetar con launchPersistentContext. El smoke test no reemplaza la prueba manual, pero evita publicar una extensión que ni siquiera inyecta el content script.
{
"type": "module",
"scripts": {
"smoke": "node playwright-extension-smoke.mjs"
},
"devDependencies": {
"playwright": "^1.52.0"
}
}
import { chromium } from "playwright";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const extensionPath = path.resolve(__dirname);
const userDataDir = path.resolve(__dirname, ".pw-extension-profile");
const context = await chromium.launchPersistentContext(userDataDir, {
headless: false,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`
]
});
const page = await context.newPage();
await page.goto("https://example.com/");
await page.waitForFunction(() => {
return document.documentElement.dataset.ccMv3Highlighter === "ready";
});
console.log("Extension content script is loaded on https://example.com/");
await page.waitForTimeout(3000);
await context.close();
npm install
npm run smoke
Checklist manual: abre chrome://extensions, activa Developer mode, carga la carpeta con Load unpacked, visita https://example.com/, selecciona texto y usa “Highlight selected text”. También revisa la consola del service worker.
Errores, empaquetado y resultado práctico
Los errores más comunes son añadir tabs sin necesitarlo, meter secretos en el content script, confiar en memoria del service worker, reemplazar HTML con innerHTML y dejar <all_urls> después de depurar. Cada uno complica la revisión y aumenta el riesgo real.
Antes de publicar, sigue Prepare your Extension. Prepara iconos, capturas, privacidad, explicación de permisos y descripción. Empaqueta solo los archivos de extensión:
zip -r mv3-highlighter.zip manifest.json service-worker.js content-script.js
Compress-Archive -Path manifest.json,service-worker.js,content-script.js -DestinationPath mv3-highlighter.zip -Force
La monetización no está solo en el código. Está en ayudar a equipos a convertir permisos, seguridad, pruebas y publicación en un proceso repetible. En formación y consultoría de Claude Code podemos revisar MV3, prompts, checklist y explicación de permisos con un repositorio real. Para avanzar por cuenta propia, consulta la biblioteca de productos.
Nota práctica: cuando Masa probó este patrón, el problema más útil no fue la sintaxis, sino justificar permisos. Con <all_urls> la demo sale rápido, pero la explicación es débil. Al limitarlo a https://example.com/* y usar solo contextMenus y storage, la advertencia de Chrome fue menor y la revisión de Claude Code resultó más concreta. En MV3, el hito real no es “funciona”, sino “funciona con permisos explicables”.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.