Use Cases (Atualizado: 02/06/2026)

Desenvolver extensões Chrome com Claude Code: MV3, mensagens e permissões mínimas

Crie uma extensão Chrome MV3 com Claude Code: manifest, service worker, content script, permissões, storage e testes.

Desenvolver extensões Chrome com Claude Code: MV3, mensagens e permissões mínimas

Comece pelo limite da extensão

Uma extensão Chrome parece pequena, mas é um produto completo em miniatura. Ela envolve manifest.json, Manifest V3, service worker, content script, mensagens, permissões, armazenamento, revisão de segurança, empacotamento e uma explicação clara para a Chrome Web Store. Se você pedir apenas “crie uma extensão Chrome” ao Claude Code, ele pode gerar um projeto grande com <all_urls>, tabs, popup, React, Vite e scripts externos.

Aqui vamos construir uma versão controlada: uma extensão Manifest V3 que destaca texto selecionado em https://example.com/* usando o menu de contexto. Ela usa JavaScript simples, chrome.storage.local, um service worker, um content script e runtime messages. Não usamos permissões amplas, código remoto nem dependências desnecessárias. Assim o exemplo fica copiável, testável e fácil de explicar.

Os termos importam. Manifest V3 é o formato atual do manifesto de extensões Chrome. O service worker é um processo de fundo orientado a eventos, que pode parar entre execuções. O content script é injetado em páginas e pode ler ou alterar o DOM. Message passing é a troca de mensagens JSON entre contextos da extensão. Quando aparecer a palavra harness em desenvolvimento com agentes, pense em uma “base de apoio” para o agente trabalhar com segurança.

Use documentação oficial durante a revisão: Chrome Extensions Manifest, About extension service workers, Chrome Content scripts, MDN Content scripts, Chrome Declare permissions e MDN permissions. Para prompts melhores, veja também dicas de produtividade com Claude Code.

Três casos de uso práticos

O primeiro caso é revisão de documentação interna. A extensão pode ser limitada a https://docs.example.com/* e destacar termos de produto em notas de release. Como o padrão de URL é estreito, a justificativa de permissão fica simples.

O segundo caso é suporte ao cliente. Um atendente pode selecionar um número de pedido em uma tela administrativa e usar o menu de contexto para destacar ou copiar a informação. Como pode haver dados pessoais, peça ao Claude Code uma tabela com o que a extensão lê, armazena, envia e ignora.

O terceiro caso é QA editorial e SEO. Um content script pode verificar tamanho de description, quantidade de h2, links internos e links oficiais externos em uma página de rascunho. Isso combina bem com verificações de CLI em desenvolvimento de ferramentas CLI com Claude Code e com práticas de segurança com Claude Code.

Prompt para Claude Code

O prompt precisa trazer função, restrições e validação. Em MV3, deixe explícito que o service worker não é persistente e que permissões amplas não são aceitáveis.

Crie uma extensão Chrome Manifest V3 de exemplo.

Requisitos:
- Limitar a URL alvo a https://example.com/*
- Adicionar um item de menu de contexto para destacar texto selecionado
- Usar JavaScript puro: manifest.json, service-worker.js, content-script.js
- Salvar enabled e color em chrome.storage.local
- Comunicar com o content script usando runtime messages
- Não usar <all_urls>, tabs, eval, scripts CDN externos ou código remoto
- Adicionar um smoke script Playwright que confirme o carregamento da extensão
- Incluir uma tabela de revisão de permissões baseada na documentação do Chrome

A regra é manter a primeira versão pequena. Se Claude Code adicionar popup, options page, ícones, bundler e permissões amplas, peça para reduzir antes de revisar comportamento.

Estrutura e Manifest

Esta pasta pode ser carregada diretamente com “Load unpacked” em 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"
    }
  ]
}
ÁreaConfiguraçãoMotivoEvitado
Permissões APIcontextMenus, storageMenu e configuraçõestabs, scripting, downloads
Acesso a páginashttps://example.com/*Um domínio de demonstração<all_urls>
Host permissionsNenhumaO content script estático bastaPermissões amplas
Código remotoNenhumRevisão MV3 mais simplesCDN, eval

Service worker

O service worker recebe eventos e envia mensagens ao content script. Como ele pode parar entre eventos, as configurações precisam ser lidas do storage quando necessário.

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;
});

O erro comum é esquecer return true quando sendResponse é chamado depois de uma Promise. Confira esse trecho com Chrome Message passing.

Content script

O content script altera o DOM, mas não deve armazenar chaves, tokens ou dados sensíveis. Ele espera uma mensagem, destaca o texto e grava um marcador data-* para o teste Playwright.

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;
});

O exemplo é simples de propósito. Ele destaca a primeira ocorrência em cada text node e evita formulários, scripts, estilos e áreas editáveis. Em produção, teste múltiplas ocorrências, Shadow DOM, conteúdo dinâmico e conflito com elementos mark.

Playwright e checklist manual

Com launchPersistentContext, Playwright carrega uma extensão não empacotada e verifica se o content script entrou na página.

{
  "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

Teste manual: abra chrome://extensions, ative Developer mode, carregue a pasta com Load unpacked, visite https://example.com/, selecione parte do título e clique em “Highlight selected text”. Confira também o console do service worker.

Armadilhas, pacote e resultado prático

As armadilhas mais comuns são adicionar tabs cedo demais, colocar segredos no content script, confiar em memória do service worker, substituir DOM com innerHTML e esquecer <all_urls> depois do debug.

Antes de publicar, siga Prepare your Extension. Prepare ícones, screenshots, política de privacidade, explicação de permissões e descrição curta. Empacote só os arquivos da extensão:

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

A monetização está menos no código gerado e mais no processo: revisão de permissões, limites de segurança, testes e publicação. A ClaudeCodeLab ajuda equipes com treinamento e consultoria em Claude Code, revisando MV3, prompts, checklists e justificativas de permissão em repositórios reais. Para estudar sozinho, veja a biblioteca de produtos.

Nota prática: quando Masa testou esse padrão, o primeiro problema útil não foi a sintaxe, e sim a justificativa das permissões. Com <all_urls> a demonstração é rápida, mas a explicação fica fraca. Ao limitar para https://example.com/* e manter apenas contextMenus e storage, o aviso do Chrome ficou menor e a revisão do Claude Code ficou mais objetiva. Em MV3, o marco real não é “funciona”, mas “funciona com permissões explicáveis”.

#Claude Code #browser extension #Chrome extension #Manifest V3 #JavaScript
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.