manifest.json to declare metadata, permissions, and capabilities. The manifest is validated and automatically patched for WebKit compatibility during installation.
Basic structure
Required fields
Every extension must include these fields:Manifest format version. Supported:
2 or 3Extension name displayed in UI. Supports localization via
__MSG_key__ syntaxExtension version string (e.g.,
"1.2.3")Common fields
Extension description shown in extension manager
Icon sizes:
16, 32, 48, 64, 128. Nook extracts the largest available icon.Toolbar action configuration (MV3). Replaces
browser_action from MV2.Permissions
Standard permissions
Permissions granted at install time. All requested permissions are automatically granted.
Permissions that require runtime
browser.permissions.request() callURL match patterns for site access (MV3)
Permission grant model
Content scripts
Scripts injected into web pages
Execution worlds
World isolation requires macOS 15.5+. Nook automatically patches manifests to add world support.
ISOLATED vs MAIN world
ISOLATED vs MAIN world
- ISOLATED (default): Content scripts run in isolated JavaScript context with
browser.*API access - MAIN: Content scripts run in page context, can access page variables but lose
browser.*APIs
Background scripts
Manifest V3 (service worker)
Manifest V2 (persistent page)
WebKit compatibility patches
Nook automatically patches manifests during installation to ensure WebKit compatibility. This happens inpatchManifestForWebKit().
Automatic patches applied
Externally connectable bridge
Problem: Pages calling Nook automatically adds:
browser.runtime.sendMessage(SAFARI_EXT_ID, msg) fail because Safari extension IDs don’t match WKWebExtension IDs.Solution: Nook injects a two-layer bridge when externally_connectable is present:- PAGE world script: Intercepts
browser.runtime.sendMessage()calls - ISOLATED world script (
nook_bridge.js): Relays messages viawindow.postMessage()
MV2 scripting permission
Problem: MV2 extensions may use
chrome.scripting API if available, but fail silently without the permission.Solution: Auto-inject "scripting" permission for all MV2 extensions:Bitwarden iframe bridge
Problem: Bitwarden’s autofill iframe sends height updates via Applied automatically for extensions with
parent.postMessage(), but content scripts in ISOLATED world don’t receive them.Solution: Inject MAIN world script nook_iframe_bridge.js that forwards iframe messages:nook_iframe_bridge.js (auto-generated)
"Bitwarden" in the name.Externally connectable
Theexternally_connectable field allows web pages to communicate with your extension:
Bridge architecture
Nook implements a sophisticated two-layer bridge:How the bridge works
How the bridge works
- PAGE world polyfill wraps
browser.runtime.sendMessage()and.connect() - Calls are relayed via
window.postMessage()to ISOLATED world - ISOLATED world bridge (
nook_bridge.js) receives messages - Real
browser.runtime.sendMessage()called with proper extension ID - Response forwarded back to PAGE world via
window.postMessage()
externally_connectable is detected.Localization
Use__MSG_key__ syntax to reference localized strings:
manifest.json
_locales/en/messages.json
- User’s current locale (e.g.,
en_US) - Language code only (e.g.,
en) default_localefrom manifest
Complete example
manifest.json
Next steps
API reference
Explore available browser APIs
Debugging
Debug manifest issues and extension behavior