Skip to content
Back to skills

Tpl Backend Chrome Extension Mv3

ASecurity

Template do pack (backend/12-chrome-extension-mv3.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.

  • 11 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
developmenttypescriptreacttestingapibackend

Works with

  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-backend-chrome-extension-mv3 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tpl Backend Chrome Extension Mv3?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tpl Backend Chrome Extension Mv3
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tpl-backend-chrome-extension-mv3/badge)](https://www.skillsdirectory.com/skills/artubss-tpl-backend-chrome-extension-mv3)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tpl-backend-chrome-extension-mv3
description: Template do pack (backend/12-chrome-extension-mv3.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
metadata:
  version: 1.0.0
  source_template: backend/12-chrome-extension-mv3.md
  generated_by: install_pack_templates_as_claude_skills
---

# PROJECT: Chrome Extension Manifest V3

Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `backend/12-chrome-extension-mv3.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.

## Conteudo do template

## STACK
- **Platform:** Chrome Extension Manifest V3
- **Language:** TypeScript 5.x (strict)
- **UI Framework:** React 19 (popup + options pages)
- **Build tool:** Vite + CRXJS v2
- **Styling:** Tailwind CSS v3
- **State:** Zustand (popup) + chrome.storage.sync
- **Testing:** Vitest + happy-dom (unit), Playwright (E2E)

---

## PROJECT STRUCTURE
```
src/
├── background/
│   └── service-worker.ts     # Background service worker
├── content/
│   ├── index.ts              # Content script entry
│   └── injected.ts           # Injected script (isolated world)
├── popup/
│   ├── index.html
│   ├── App.tsx
│   └── components/
├── options/
│   ├── index.html
│   └── App.tsx
├── shared/
│   ├── messages.ts           # Message type definitions
│   ├── storage.ts            # chrome.storage wrappers
│   └── utils.ts
public/
└── icons/
manifest.json
vite.config.ts
tsconfig.json
```

---

## ARCHITECTURE RULES
1. **Service worker is stateless** — it can be terminated at any time; never rely on in-memory state; use `chrome.storage`.
2. **Content scripts are isolated** — they run in the page context but cannot access page JS variables directly; use `window.postMessage` or injected scripts.
3. **Message passing is typed** — define a discriminated union `AppMessage`; use `chrome.runtime.sendMessage` only with typed messages.
4. **Permissions are minimal** — request only what is needed; do not use `<all_urls>` if a specific match pattern suffices.
5. **No eval / dynamic code execution** — MV3 CSP blocks it; build everything at compile time.
6. **storage.sync for user preferences** — `storage.local` for large or sensitive data.
7. **CRXJS handles HMR** — never manually reload in development.

---

## MANIFEST V3 CONFIGURATION

```json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "Production-ready Chrome Extension",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "https://api.example.com/*"
  ],
  "background": {
    "service_worker": "src/background/service-worker.ts",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["src/content/index.ts"],
      "run_at": "document_idle"
    }
  ],
  "action": {
    "default_popup": "src/popup/index.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "options_ui": {
    "page": "src/options/index.html",
    "open_in_tab": true
  }
}
```

---

## TYPED MESSAGE PASSING

```typescript
// src/shared/messages.ts
export type AppMessage =
  | { type: 'GET_PAGE_DATA' }
  | { type: 'PAGE_DATA'; payload: { title: string; url: string } }
  | { type: 'SET_BADGE'; count: number }
  | { type: 'OPEN_OPTIONS' }

// src/background/service-worker.ts
import type { AppMessage } from '../shared/messages'

// Service worker: listen for messages from popup or content
chrome.runtime.onMessage.addListener(
  (message: AppMessage, sender, sendResponse) => {
    if (message.type === 'GET_PAGE_DATA') {
      // Query active tab
      chrome.tabs.query({ active: true, currentWindow: true }, async ([tab]) => {
        if (!tab.id) return
        const results = await chrome.scripting.executeScript({
          target: { tabId: tab.id },
          func: () => ({ title: document.title, url: location.href }),
        })
        sendResponse({ type: 'PAGE_DATA', payload: results[0].result })
      })
      return true // keeps channel open for async response
    }

    if (message.type === 'SET_BADGE') {
      chrome.action.setBadgeText({ text: String(message.count) })
      chrome.action.setBadgeBackgroundColor({ color: '#6366f1' })
    }
  }
)

// src/popup/App.tsx — sending messages
const getPageData = async () => {
  const response = await chrome.runtime.sendMessage<AppMessage, AppMessage>({
    type: 'GET_PAGE_DATA',
  })
  if (response.type === 'PAGE_DATA') setData(response.payload)
}
```

---

## STORAGE API WRAPPER

```typescript
// src/shared/storage.ts
export interface ExtConfig {
  theme:       'light' | 'dark'
  apiKey:      string
  autoCapture: boolean
}

const DEFAULTS: ExtConfig = {
  theme: 'light', apiKey: '', autoCapture: false,
}

export const storage = {
  async get(): Promise<ExtConfig> {
    const raw = await chrome.storage.sync.get(DEFAULTS)
    return raw as ExtConfig
  },
  async set(patch: Partial<ExtConfig>): Promise<void> {
    await chrome.storage.sync.set(patch)
  },
  onChange(cb: (changes: Partial<ExtConfig>) => void): void {
    chrome.storage.sync.onChanged.addListener((changes) => {
      const patch: Partial<ExtConfig> = {}
      for (const [key, { newValue }] of Object.entries(changes)) {
        (patch as Record<string, unknown>)[key] = newValue
      }
      cb(patch)
    })
  },
}
```

---

## CONTENT SCRIPT ISOLATION

```typescript
// src/content/index.ts — runs in ISOLATED world; can read DOM
import type { AppMessage } from '../shared/messages'

// Observe DOM changes and notify background
const observer = new MutationObserver(() => {
  const count = document.querySelectorAll('[data-item]').length
  chrome.runtime.sendMessage<AppMessage>({ type: 'SET_BADGE', count })
})
observer.observe(document.body, { childList: true, subtree: true })

// src/content/injected.ts — access page JS from MAIN world
// Injected via chrome.scripting.executeScript with world: "MAIN"
// NOTE: Cannot use chrome.* APIs here
window.addEventListener('message', (event) => {
  if (event.source !== window || event.data?.source !== 'my-ext') return
  // Bridge page data → isolated context via postMessage
  window.postMessage({ source: 'my-ext-page', data: window.__appState }, '*')
})
```

---

## ROUTING TABLE (Message Type → Handler)

| Message Type | From | To | Action |
|-------------|------|----|--------|
| `GET_PAGE_DATA` | Popup | Service Worker | Execute script in active tab |
| `PAGE_DATA` | Service Worker | Popup | Return title + URL |
| `SET_BADGE` | Content Script | Service Worker | Update badge count |
| `OPEN_OPTIONS` | Popup | Service Worker | `chrome.runtime.openOptionsPage()` |
| `storage.get` | Any | chrome.storage.sync | Read config |
| `storage.set` | Options page | chrome.storage.sync | Write config |
| content→bg | Content | Service Worker | DOM event notification |
| tab.onUpdated | — | Service Worker | React on navigation |

---

## QUALITY GATES
- [ ] `tsc --noEmit` — zero errors
- [ ] No `eval`, `new Function()`, or `innerHTML = userInput`
- [ ] Permissions declared in `manifest.json` — nothing requested at runtime that isn't declared
- [ ] `vitest run` passes all unit tests
- [ ] `npm run build` produces clean `dist/` (no TS source, no sourcemaps in prod)
- [ ] Extension loads in Chrome without errors in `chrome://extensions/`
- [ ] Service worker message handlers all `return true` if async
- [ ] Content script does not access `chrome.storage` directly in `MAIN` world scripts

---

## FORBIDDEN
- ❌ `eval()` or `document.write()` — blocked by MV3 CSP
- ❌ Remote scripts loaded at runtime — all code must be bundled
- ❌ `chrome.tabs.executeScript` (MV2 API) — use `chrome.scripting.executeScript`
- ❌ Storing tokens in `localStorage` — use `chrome.storage.local`
- ❌ `background.js` as classic script with DOM access — SW has no DOM
- ❌ `<all_urls>` host permission without clear justification
- ❌ Hardcoded API keys in extension source — fetch from storage
- ❌ Long-running tasks in service worker without keepalive strategy

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.