From craft-workspace-systembolaget-plus
Audits and converts Chrome extensions from Manifest V2 to V3, covering manifest changes, service workers, API replacements, security rules, and Web Store compliance risks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/craft-workspace-systembolaget-plus:chrome-extension-mv2-to-mv3The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are migrating a Chrome extension from Manifest V2 to Manifest V3.
You are migrating a Chrome extension from Manifest V2 to Manifest V3.
Your job is not only to change files, but to ensure the extension conforms to modern Chrome extension architecture, security restrictions, and review expectations.
Before editing, inspect:
manifest.jsonchrome.webRequest listenerseval, new Function, or injected code stringschrome.browserActionchrome.pageActionchrome.extension.*chrome.tabs.executeScriptchrome.tabs.insertCSSchrome.tabs.removeCSSchrome.runtime.getBackgroundPagechrome.extension.getBackgroundPageChange:
"manifest_version": 2 → "manifest_version": 3In MV3:
permissionshost_permissionsoptional_host_permissionsImportant:
content_scripts[].matches stays under content_scriptspermissionsConvert:
browser_action → actionpage_action → actionAlso replace code usage:
chrome.browserAction.* / chrome.pageAction.* → chrome.action.*If old behavior depended on page-action visibility:
chrome.action.enable() / disable() per tabManifest V3 uses:
background.service_workerbackground.type: "module" when ES modules are neededRemove MV2-only concepts:
Convert from a flat array to MV3 objects:
resourcesmatches and/or extension_idsuse_dynamic_urlDo not overexpose resources. Restrict matches as tightly as possible.
MV3 requires content_security_policy to be an object, typically with:
extension_pagessandboxDo not use insecure CSP values that allow remote code execution in extension pages.
Manifest V3 background logic runs in an extension service worker.
Assume all of the following:
windowPrefer listeners such as:
chrome.runtime.onInstalledchrome.runtime.onStartupchrome.action.onClickedchrome.tabs.*chrome.runtime.onMessageDo not rely on globals surviving forever. Persist state using:
chrome.storage.localchrome.storage.sync if appropriateListeners should be declared at module top level so they exist when the worker starts.
Long-running polling/background loops often need redesign using:
If former background code used DOM APIs, canvas, audio, or document APIs:
Use an offscreen document when you need hidden DOM access but not a visible UI.
If you use an offscreen document:
"offscreen" permissionchrome.runtime messagingReplace:
chrome.tabs.executeScript → chrome.scripting.executeScriptchrome.tabs.insertCSS → chrome.scripting.insertCSSchrome.tabs.removeCSS → chrome.scripting.removeCSSManifest requirements:
"scripting" permissionactiveTabMigration notes:
files is an array in MV3target: { tabId }func + args or bundled filesReplace or redesign calls such as:
chrome.runtime.getBackgroundPage()chrome.extension.getBackgroundPage()chrome.extension.getExtensionTabs()Use message passing instead:
chrome.runtime.sendMessagechrome.runtime.onMessagePrefer chrome.runtime.* over chrome.extension.*.
Examples:
chrome.extension.sendMessage → chrome.runtime.sendMessagechrome.extension.getURL → chrome.runtime.getURLchrome.extension.onRequest → chrome.runtime.onMessageWhere Chrome APIs support promises:
await / promise chainswebRequestMV3 generally requires replacing blocking interception/modification with declarativeNetRequest (DNR).
Audit for:
onBeforeRequestonBeforeSendHeadersonHeadersReceivedwebRequestBlockingReframe behavior as declarative rules:
blockredirectmodifyHeadersManifest changes commonly include:
webRequestBlockingdeclarativeNetRequest or declarativeNetRequestWithHostAccess as neededhost_permissions where requireddeclarative_net_request.rule_resourcesImportant nuance:
webRequestBlocking may still be allowedDo not do a superficial API rename. Rewrite behavior around DNR capabilities and limitations.
Remove or replace:
Allowed pattern:
Eliminate or redesign uses of:
eval()new Function()Preferred replacements:
chrome.scripting.executeScript({ files: [...] })chrome.scripting.executeScript({ func, args })Ensure extension page CSP does not permit unsafe remote execution. Use MV3-compatible values only. Be especially careful with:
script-srcobject-srcworker-srcIf libraries were loaded from CDNs:
Check extension pages for:
onclick, etc.)Preferred fixes:
After migration:
activeTab over broad host access when feasibleAfter edits, verify at minimum:
When completing a migration, provide:
Use this structure when reporting work:
Escalate and explain carefully if you find:
webRequest logic that may not map perfectly to DNRDo not:
A successful migration means:
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub hemmingsson/systembolaget-plus