🌐 Detecting your location…

Como construir uma extensão do Chrome com Manifest V3 em 2026: guia completo

⏱️8 min read  ·  1,559 words

O Manifest V3 agora é a única opção para novas extensões do Chrome e muda significativamente a arquitetura: as páginas em segundo plano tornaram-se service workers efêmeros, a execução remota de código foi proibida e a interceptação de rede foi movida para uma API declarativa. Se você aprendeu o desenvolvimento de extensões na V2, vários hábitos não funcionam mais. Este guia cria uma extensão completa e funcional do zero sob as regras V3.

O que estamos construindo

Uma extensão de anotação de página: adiciona um pop-up na barra de ferramentas, injeta um script de conteúdo que destaca o texto selecionado em qualquer página, salva os destaques por URL e os sincroniza por meio dechrome.storage. Ele exercita todas as partes da V3 que você realmente usará – interface de usuário pop-up, script de conteúdo, service worker, mensagens, armazenamento e permissões.

Etapa 1: Estrutura do Projeto e Manifesto

Crie um diretório com esses arquivos. O manifesto é o ponto de entrada e o Chrome o lê primeiro.

my-extension/
  manifest.json
  background.js
  content.js
  popup.html
  popup.js
  styles.css
  icons/icon16.png icon48.png icon128.png

O manifesto declara a versão 3, seus pontos de entrada e permissões. Mantenha as permissões mínimas – cada permissão extra retarda a revisão e assusta os usuários no momento da instalação.

{
  "manifest_version": 3,
  "name": "Page Highlighter",
  "version": "1.0.0",
  "description": "Highlight and save text on any page.",
  "permissions": ["storage", "activeTab", "scripting"],
  "host_permissions": ["http://*/*", "https://*/*"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["content.js"],
      "css": ["styles.css"],
      "run_at": "document_idle"
    }
  ],
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

PrefiraactiveTab sobre amplas permissões de host quando puder. Ele concede acesso à guia atual somente depois que o usuário clica no ícone da extensão, o que é muito mais fácil de justificar na revisão.

Etapa 2: O Service Worker não é uma página de plano de fundo

Esta é a maior mudança do V3. Seu script em segundo plano é um service worker que o Chrome encerra quando está inativo e reinicia no próximo evento. Qualquer variável definida no nível superior desaparece após o encerramento. O estado deve morar emchrome.storage, não na memória.

// background.js

// WRONG under V3 — this resets every time the worker restarts.
// let highlightCount = 0;

chrome.runtime.onInstalled.addListener(async () => {
  const { highlights } = await chrome.storage.local.get('highlights');
  if (!highlights) {
    await chrome.storage.local.set({ highlights: {} });
  }
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'SAVE_HIGHLIGHT') {
    saveHighlight(message.payload).then(() => sendResponse({ ok: true }));
    // Returning true keeps the message channel open for the async reply.
    return true;
  }
});

async function saveHighlight({ url, text }) {
  const { highlights = {} } = await chrome.storage.local.get('highlights');
  const forPage = highlights[url] || [];
  forPage.push({ text, createdAt: Date.now() });
  highlights[url] = forPage;
  await chrome.storage.local.set({ highlights });
}

Oreturn true no ouvinte de mensagem é fácil de perder e causa um bug clássico: seu manipulador assíncrono é concluído, mas o remetente nunca recebe a resposta, porque o Chrome fechou o canal quando o ouvinte retornou indefinido.

Etapa 3: O script de conteúdo

Os scripts de conteúdo são executados no DOM da página, mas em um mundo JavaScript isolado. Eles podem ler e modificar o DOM, mas não podem ver as próprias variáveis JavaScript da página. Esse isolamento é um recurso de segurança, não uma limitação para contornar.

// content.js
document.addEventListener('mouseup', async () => {
  const selection = window.getSelection();
  const text = selection.toString().trim();
  if (text.length < 3) return;

  const range = selection.getRangeAt(0);
  const mark = document.createElement('mark');
  mark.className = 'ext-highlight';

  try {
    range.surroundContents(mark);
  } catch {
    // surroundContents throws when the selection crosses element boundaries.
    return;
  }

  await chrome.runtime.sendMessage({
    type: 'SAVE_HIGHLIGHT',
    payload: { url: location.href, text }
  });

  selection.removeAllRanges();
});

Observe otry/catch por aísurroundContents. É lançado sempre que a seleção abrange vários elementos, o que acontece constantemente em páginas reais. Lidar com isso é a diferença entre uma extensão que funciona na sua página de teste e outra que funciona em qualquer lugar.

Etapa 4: o pop-up

O pop-up é uma página da web comum com acesso a APIs de extensão. Ele é destruído toda vez que é fechado, portanto, trate-o como sem estado e leia-o do armazenamento aberto.

<!-- popup.html -->
<!DOCTYPE html>
<html>
  <head><meta charset="utf-8"></head>
  <body style="width:320px;font:14px system-ui;padding:12px">
    <h1 style="font-size:15px;margin:0 0 8px">Highlights on this page</h1>
    <ul id="list"></ul>
    <script src="popup.js"></script>
  </body>
</html>

Os scripts embutidos são bloqueados pela política de segurança de conteúdo de extensão, portanto<script src> a referência é obrigatória — você não pode colocar JavaScript diretamente no HTML.

// popup.js
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const { highlights = {} } = await chrome.storage.local.get('highlights');
const items = highlights[tab.url] || [];

const list = document.getElementById('list');
if (items.length === 0) {
  list.innerHTML = '<li>No highlights yet.</li>';
} else {
  for (const item of items) {
    const li = document.createElement('li');
    li.textContent = item.text;   // textContent, never innerHTML
    list.appendChild(li);
  }
}

UsartextContent em vez deinnerHTML para qualquer valor originado de uma página da web. O texto destacado são dados controlados pelo invasor, e construir HTML a partir dele é uma rota XSS direta para o contexto privilegiado da sua extensão.

Etapa 5: opções de armazenamento

O Chrome oferece três áreas de armazenamento e a escolha errada causa falhas silenciosas.

Área Cota Usar para
storage.local ~10MB (ilimitado com permissão) Dados em massa, conteúdo em cache
storage.sync ~100 KB no total, 8 KB por item Pequenas configurações de usuário, sincronizadas entre dispositivos
storage.session ~10 MB, apenas memória Dados que não devem sobreviver à reinicialização do navegador

O erro comum é colocar conteúdo do usuário emstorage.sync porque a sincronização parece desejável. O limite de 8 KB por item é atingido rapidamente e as gravações falham — muitas vezes silenciosamente, se você não verificar se há erros.

Etapa 6: Interceptação de rede alterada

O bloqueiowebRequest A API desapareceu. Se você precisar bloquear ou redirecionar solicitações, usedeclarativeNetRequest, onde você registra regras estáticas que o próprio Chrome avalia. Sua extensão nunca vê a solicitação.

{
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [{
      "id": "ruleset_1",
      "enabled": true,
      "path": "rules.json"
    }]
  }
}

Isso é mais restritivo por natureza e é por isso que vários bloqueadores de anúncios tiveram que ser reescritos. Se o valor principal da sua extensão depende da inspeção dos corpos da solicitação em tempo de execução, a V3 pode realmente não suportá-la.

Etapa 7: Carregar e depurar

Abrirchrome://extensions, ative o modo de desenvolvedor e escolha “Carregar descompactado”. Existem três consoles separados e saber qual abrir economiza horas:

  • Trabalhador de serviço: clique no link “trabalhador de serviço” em seu cartão de extensão
  • Pop-up: clique com o botão direito no pop-up e escolha Inspecionar
  • Roteiro de conteúdo: o console do DevTools da página normal, com o seletor de contexto definido para sua extensão

Se o service worker parecer morto, isso é esperado – ele termina após cerca de 30 segundos de inatividade. Acione um evento e ele será reiniciado.

Etapa 8: Publicação

Compacte o conteúdo do diretório de extensão (não a pasta anexa) e faça upload por meio do Painel do desenvolvedor da Chrome Web Store, que exige uma taxa única de registro. O tempo de revisão depende muito de suas permissões: uma extensão usando apenasactiveTab estorage normalmente limpa a revisão rapidamente, enquanto amplas permissões de host maisscripting atrair revisão manual e pode levar muito mais tempo. Escreva uma justificativa clara para cada permissão na listagem – os revisores rejeitam explicações vagas.

Erros Comuns

Armazenando estado em globais de service workers. Ele desaparece na rescisão. Usarchrome.storage.

Esquecendoreturn true em ouvintes de mensagens assíncronas. A resposta nunca chega e o fracasso é silencioso.

Solicitando<all_urls> quandoactiveTab faria. Isso retarda a revisão e reduz as instalações.

Carregando código remoto. V3 proíbe isso completamente. Todo o código executável deve ser enviado no pacote, o que significa que não há scripts CDN e nãoeval.

Conclusão

O desenvolvimento do Manifest V3 se resume a algumas disciplinas:trate o trabalhador de serviço como apátrida e mantenha todo o estado emchrome.storage, solicite as permissões mais restritas que funcionam, usetextContent para qualquer coisa proveniente de uma página, lembre-sereturn true para manipuladores de mensagens assíncronas e envie cada linha de código dentro do pacote. Construa com essas restrições desde o início e a plataforma permanecerá fora do seu caminho – adaptá-las em um design em forma de V2 é onde reside a dor.

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work — which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা